In this lab, we are going to work with vector data, which we’ve talked about last week. First, we are going to geocode addresses. We are going to work with a dataset of cancer patients across California, as well as Census data on socioeconomic factors. We will also talk about how to visualize data.

The objectives of this guide are to teach you:

  1. How to geocode addresses
  2. How to bring in and visualize point data
  3. How to download Census data using the Census API
  4. How to conduct exploratory data analysis

Let’s get cracking!


Open up an R Markdown file

We hopefully remember some of this from last week in Lab 2, but let’s open an R Markdown file by clicking on File at the top menu in RStudio, select New File, and then R Markdown…. A window should pop up. In that window, for title, put in “Lab 3”. For author, put your name. Leave the HTML radio button clicked, and select OK. A new R Markdown file should pop up in the top left window.


What packages do we need?

Let’s load some packages that we will need this week. We need to load any packages we previously installed using the function library(). Remember, install once, load every time. And if it gives you an error for no package called..., then we need to install those packages using install.packages(). So when using a package, library() should always be at the top of your R Markdown.

library(tidygeocoder)
library(sf)
library(MapGAM)
library(tidyverse)
library(tidycensus)
library(flextable)
library(tmap)


Geocoding

First, we are going to tackle how we take addresses and convert them to spatial data (latitude and longitude). So, let’s say we wanted to map all of the marijuana dispensaries across San Francisco. Let’s download a .csv of these addresses from the Github site, then take a look at the dataset.

download.file("https://raw.githubusercontent.com/pjames-ucdavis/SPH215/refs/heads/main/san_francisco_active_marijuana_retailers.csv", "san_francisco_active_marijuana_retailers.csv", mode = "wb")

sf_mj <- read_csv("san_francisco_active_marijuana_retailers.csv")
## Rows: 33 Columns: 10
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (10): License Number, License Type, Business Owner, Business Structure, ...
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
head(sf_mj)                  
## # A tibble: 6 × 10
##   `License Number` `License Type`          `Business Owner` `Business Structure`
##   <chr>            <chr>                   <chr>            <chr>               
## 1 C10-0000614-LIC  Cannabis - Retailer Li… Terry Muller     Limited Liability C…
## 2 C10-0000586-LIC  Cannabis - Retailer Li… Jeremy Goodin    Corporation         
## 3 C10-0000587-LIC  Cannabis - Retailer Li… Justin Jarin     Corporation         
## 4 C10-0000539-LIC  Cannabis - Retailer Li… Ondyn Herschelle Corporation         
## 5 C10-0000522-LIC  Cannabis - Retailer Li… Ryan Hudson      Limited Liability C…
## 6 C10-0000523-LIC  Cannabis - Retailer Li… Ryan Hudson      Limited Liability C…
## # ℹ 6 more variables: `Premise Address` <chr>, Status <chr>,
## #   `Issue Date` <chr>, `Expiration Date` <chr>, Activities <chr>,
## #   `Adult-Use/Medicinal` <chr>


OK, some interesting columns there, and we have Premise Address as a column that we might want to make spatial. Let’s look closer at that.

head(sf_mj$`Premise Address`)
## [1] "2165 IRVING ST san francisco, CA 94122 County: SAN FRANCISCO" 
## [2] "122 10TH ST SAN FRANCISCO, CA 941032605 County: SAN FRANCISCO"
## [3] "843 Howard ST SAN FRANCISCO, CA 94103 County: SAN FRANCISCO"  
## [4] "70 SECOND ST SAN FRANCISCO, CA 94105 County: SAN FRANCISCO"   
## [5] "527 Howard ST San Francisco, CA 94105 County: SAN FRANCISCO"  
## [6] "2414 Lombard ST San Francisco, CA 94123 County: SAN FRANCISCO"


OK that column looks like what we want to geocode. But how do we take these addresses and make them into spatial information? We have to geocode them! To do so, we will use the tidygeocoder package in R. But first, we see that the addresses look a little strange. The address county is always “County: SAN FRANCISCO” so we will gsub() out that entire string.

sf_mj$`Premise Address` <- gsub(" County: SAN FRANCISCO",
                                  "", sf_mj$`Premise Address`)
head(sf_mj$`Premise Address`)
## [1] "2165 IRVING ST san francisco, CA 94122" 
## [2] "122 10TH ST SAN FRANCISCO, CA 941032605"
## [3] "843 Howard ST SAN FRANCISCO, CA 94103"  
## [4] "70 SECOND ST SAN FRANCISCO, CA 94105"   
## [5] "527 Howard ST San Francisco, CA 94105"  
## [6] "2414 Lombard ST San Francisco, CA 94123"

That looks much better.


Now let’s give a try to geocoding these addresses with the tidygeocoder package. We will use the geocode() function to add a latitude and longitude to each of our addresses in the Premise Address column. We will use the Open Street Map address database by specifying method = "osm". This will take about a minute to run, so be patient!

sf_mj_geo      <- geocode(sf_mj, "Premise Address",
                          method = "osm")
## Passing 33 addresses to the Nominatim single address geocoder
## Query completed in: 34.2 seconds
head(sf_mj_geo)
## # A tibble: 6 × 12
##   `License Number` `License Type`          `Business Owner` `Business Structure`
##   <chr>            <chr>                   <chr>            <chr>               
## 1 C10-0000614-LIC  Cannabis - Retailer Li… Terry Muller     Limited Liability C…
## 2 C10-0000586-LIC  Cannabis - Retailer Li… Jeremy Goodin    Corporation         
## 3 C10-0000587-LIC  Cannabis - Retailer Li… Justin Jarin     Corporation         
## 4 C10-0000539-LIC  Cannabis - Retailer Li… Ondyn Herschelle Corporation         
## 5 C10-0000522-LIC  Cannabis - Retailer Li… Ryan Hudson      Limited Liability C…
## 6 C10-0000523-LIC  Cannabis - Retailer Li… Ryan Hudson      Limited Liability C…
## # ℹ 8 more variables: `Premise Address` <chr>, Status <chr>,
## #   `Issue Date` <chr>, `Expiration Date` <chr>, Activities <chr>,
## #   `Adult-Use/Medicinal` <chr>, lat <dbl>, long <dbl>


Hmm, looks like some of our addresses have an NA for their lat and long. Let’s take a closer look.

summary(sf_mj_geo$lat)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
##   37.71   37.75   37.78   37.77   37.78   37.80      11
summary(sf_mj_geo$long)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
##  -122.5  -122.4  -122.4  -122.4  -122.4  -122.4      11


Looks like we have 10 addresses missing lat and 10 missing long. Let’s try this again using a different geocoding database called arcgis.

sf_mj_geo_arc      <- geocode(sf_mj, "Premise Address",
                          method = "arcgis")
## Passing 33 addresses to the ArcGIS single address geocoder
## Query completed in: 15 seconds
head(sf_mj_geo_arc)
## # A tibble: 6 × 12
##   `License Number` `License Type`          `Business Owner` `Business Structure`
##   <chr>            <chr>                   <chr>            <chr>               
## 1 C10-0000614-LIC  Cannabis - Retailer Li… Terry Muller     Limited Liability C…
## 2 C10-0000586-LIC  Cannabis - Retailer Li… Jeremy Goodin    Corporation         
## 3 C10-0000587-LIC  Cannabis - Retailer Li… Justin Jarin     Corporation         
## 4 C10-0000539-LIC  Cannabis - Retailer Li… Ondyn Herschelle Corporation         
## 5 C10-0000522-LIC  Cannabis - Retailer Li… Ryan Hudson      Limited Liability C…
## 6 C10-0000523-LIC  Cannabis - Retailer Li… Ryan Hudson      Limited Liability C…
## # ℹ 8 more variables: `Premise Address` <chr>, Status <chr>,
## #   `Issue Date` <chr>, `Expiration Date` <chr>, Activities <chr>,
## #   `Adult-Use/Medicinal` <chr>, lat <dbl>, long <dbl>
summary(sf_mj_geo_arc$lat)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   37.71   37.76   37.77   37.77   37.78   37.80
summary(sf_mj_geo_arc$long)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##  -122.5  -122.4  -122.4  -122.4  -122.4  -122.4


Woohoo! No missingness. Love to see it. OK, let’s plot these data and see how they look.

plot(sf_mj_geo_arc$long, sf_mj_geo_arc$lat)


We are in business! We have taken addresses and converted them into latitude and longitude! I think we need a badge! tidygeocoder Badge


Bonus exercise! Let’s take these addresses and reverse geocode them. That’s just a fancy way of saying that we will take latitude and longitude data and convert it into readable addresses. We use the aptly named function reverse_geocode and specify which columns to look at (lat and long), the method we want for geocoding, and what we want the address column to be named. Then we select out some columns that we aren’t really interested in. Remember, we are doing this the tidy way so we are using %>% pipes.

reverse <- sf_mj_geo_arc %>%
  reverse_geocode(lat = lat, long = long, method = 'arcgis',
                  address = address_found) %>%
  select(-`Business Owner`,-`Business Structure`,-`License Number`,-`License Type`,-Status,-`Issue Date`,-`Expiration Date`,-Activities,-`Adult-Use/Medicinal`)
## Passing 33 coordinates to the ArcGIS single coordinate geocoder
## Query completed in: 15.9 seconds
head(reverse)
## # A tibble: 6 × 4
##   `Premise Address`                         lat  long address_found             
##   <chr>                                   <dbl> <dbl> <chr>                     
## 1 2165 IRVING ST san francisco, CA 94122   37.8 -122. Smokin D's BBQ, 2181 Irvi…
## 2 122 10TH ST SAN FRANCISCO, CA 941032605  37.8 -122. Urbana Weed Dispensary SO…
## 3 843 Howard ST SAN FRANCISCO, CA 94103    37.8 -122. Comart Business Syst, 843…
## 4 70 SECOND ST SAN FRANCISCO, CA 94105     37.8 -122. Cannavine San Francisco, …
## 5 527 Howard ST San Francisco, CA 94105    37.8 -122. Fixed, 527 Howard St, Ste…
## 6 2414 Lombard ST San Francisco, CA 94123  37.8 -122. Mile 7.6 US Hwy 101 N, Sa…

Looking at Premise Address and address_found we can see that we did pretty well! Not perfect, but most are the right address or a few doors down. Well done!


sf: Our go to package for vector data

Although there are a few ways to work with vector spatial data in R, we will focus on the sf package in this course. The majority of spatial folks in R have shifted to sf for vector data, and so it makes sense to focus on it in the class.

Processing spatial data is very similar to nonspatial data thanks to the package sf, which is tidy friendly. sf stands for simple features. The Simple Features standard defines a simple feature as a representation of a real world object by a point or points that may or may not be connected by straight line segments to form lines or polygons. A feature is thought of as a thing, or an object in the real world, such as a building or a tree. A county can be a feature. As can a city and a neighborhood. Features have a geometry describing where on Earth the features are located, and they have attributes, which describe other properties.

Now let’s get our hands dirty working with some spatial data.


Vectors: Import Cancer Point Data

For this lab, we will primarily be working with the MapGAM package. If you go to the link, you can read the reference manual on the various datasets available in the package. For this lab, we will mainly be working with the CAdata dataset. While they are based on real patterns expected in observational epidemiologic studies, these data have been simulated and are for teaching purposes only. The data contain 5000 simulated ovarian cancer cases. This is a cohort with time to mortality measured, but for the purposes of our class, we will conduct simple tabular analyses looking at associations between spatial exposures with mortality at end of follow-up.

The CAdata dataset contains the following variables

  • time (follow-up time to either event of being censored)
  • event (1=dead, 0=censored)
  • X (Latitude)
  • Y (Longitude)
  • AGE (age in years)
  • INS (insurance status, categorical)


So let’s bring in the CAdata dataset and have a look at it.

#Load CAdata dataset from MapGAM package
data(CAdata)
ca_pts <- CAdata
summary(ca_pts)
##       time               event              X                 Y          
##  Min.   : 0.004068   Min.   :0.0000   Min.   :1811375   Min.   :-241999  
##  1st Qu.: 1.931247   1st Qu.:0.0000   1st Qu.:2018363   1st Qu.: -94700  
##  Median : 4.749980   Median :1.0000   Median :2325084   Median : -60386  
##  Mean   : 6.496130   Mean   :0.6062   Mean   :2230219   Mean   :  87591  
##  3rd Qu.: 9.609031   3rd Qu.:1.0000   3rd Qu.:2380230   3rd Qu.: 318280  
##  Max.   :24.997764   Max.   :1.0000   Max.   :2705633   Max.   : 770658  
##       AGE         INS      
##  Min.   :25.00   Mcd: 431  
##  1st Qu.:53.00   Mcr:1419  
##  Median :62.00   Mng:2304  
##  Mean   :61.28   Oth: 526  
##  3rd Qu.:71.00   Uni: 168  
##  Max.   :80.00   Unk: 152


OK, so the variables look great. Is it a spatial dataset that can be recognized by R? Not just yet. We know that X is likely some sort of longitude column and Y is likely some sort of latitude column, although they don’t exactly look right. We have to tell R that the X and Y coordinates are spatial data using the st_as_sf function in sf. With this command, we can specify which coordinates R should look at for longitude and latitude with the coords=c() function.


ca_pts <- st_as_sf(CAdata, coords=c("X","Y"))


Let’s check the coordinate reference system (CRS) using the st_crs command in the sf package.

st_crs(ca_pts)
## Coordinate Reference System: NA

Hmmm, NA. That still doesn’t look good. So how do we make this a spatial file? We will need to add a CRS.


Add coordinate reference system

Let’s add a CRS by using st_set_sf from the sf package. We get the CRS for this dataset from the MapGAM documentation (don’t worry–it took me forever to find this, but usually this is much easier to find). Then we will double check the CRS.

#Load the projection into an object called ca_proj

ca_proj <- "+proj=lcc +lat_1=40 +lat_2=41.66666666666666 
             +lat_0=39.33333333333334 +lon_0=-122 +x_0=2000000 
             +y_0=500000.0000000002 +ellps=GRS80 
             +datum=NAD83 +units=m +no_defs"

#Set CRS
ca_pts_crs <- st_set_crs(ca_pts, ca_proj)

#Look at dataset
summary(ca_pts_crs) 
##       time               event             AGE         INS      
##  Min.   : 0.004068   Min.   :0.0000   Min.   :25.00   Mcd: 431  
##  1st Qu.: 1.931247   1st Qu.:0.0000   1st Qu.:53.00   Mcr:1419  
##  Median : 4.749980   Median :1.0000   Median :62.00   Mng:2304  
##  Mean   : 6.496130   Mean   :0.6062   Mean   :61.28   Oth: 526  
##  3rd Qu.: 9.609031   3rd Qu.:1.0000   3rd Qu.:71.00   Uni: 168  
##  Max.   :24.997764   Max.   :1.0000   Max.   :80.00   Unk: 152  
##           geometry   
##  POINT        :5000  
##  epsg:NA      :   0  
##  +proj=lcc ...:   0  
##                      
##                      
## 
#Check the CRS
st_crs(ca_pts_crs)
## Coordinate Reference System:
##   User input: +proj=lcc +lat_1=40 +lat_2=41.66666666666666 
##              +lat_0=39.33333333333334 +lon_0=-122 +x_0=2000000 
##              +y_0=500000.0000000002 +ellps=GRS80 
##              +datum=NAD83 +units=m +no_defs 
##   wkt:
## PROJCRS["unknown",
##     BASEGEOGCRS["unknown",
##         DATUM["North American Datum 1983",
##             ELLIPSOID["GRS 1980",6378137,298.257222101,
##                 LENGTHUNIT["metre",1]],
##             ID["EPSG",6269]],
##         PRIMEM["Greenwich",0,
##             ANGLEUNIT["degree",0.0174532925199433],
##             ID["EPSG",8901]]],
##     CONVERSION["unknown",
##         METHOD["Lambert Conic Conformal (2SP)",
##             ID["EPSG",9802]],
##         PARAMETER["Latitude of false origin",39.3333333333333,
##             ANGLEUNIT["degree",0.0174532925199433],
##             ID["EPSG",8821]],
##         PARAMETER["Longitude of false origin",-122,
##             ANGLEUNIT["degree",0.0174532925199433],
##             ID["EPSG",8822]],
##         PARAMETER["Latitude of 1st standard parallel",40,
##             ANGLEUNIT["degree",0.0174532925199433],
##             ID["EPSG",8823]],
##         PARAMETER["Latitude of 2nd standard parallel",41.6666666666667,
##             ANGLEUNIT["degree",0.0174532925199433],
##             ID["EPSG",8824]],
##         PARAMETER["Easting at false origin",2000000,
##             LENGTHUNIT["metre",1],
##             ID["EPSG",8826]],
##         PARAMETER["Northing at false origin",500000,
##             LENGTHUNIT["metre",1],
##             ID["EPSG",8827]]],
##     CS[Cartesian,2],
##         AXIS["(E)",east,
##             ORDER[1],
##             LENGTHUNIT["metre",1,
##                 ID["EPSG",9001]]],
##         AXIS["(N)",north,
##             ORDER[2],
##             LENGTHUNIT["metre",1,
##                 ID["EPSG",9001]]]]


tmap: Mapping vector data

Nice! We have a spatial dataset. That geometry column is how sf stores the geographic data, and latitude and longitude there are looking a bit more like we would expect. And we definitely have a full CRS with all sorts of info. OK, let’s plot our data to make sure they look spatial! We will use the tmap package to plot the points. We will first specify the tmap_mode of “view” which is interactive. There’s also a “plot” option which is nice for making exportable figures. We will then create an object called cancer_map and then add a layer with tm_shape(). This allows us to combine several maps into one, or to add layers on top of each other. Then we have to specify a level of that layer to display. Here we will use tm_dots() to to plot the points. In our options, we specify the size with size=.

tmap_mode("view")
## ℹ tmap modes "plot" - "view"
## ℹ toggle with `tmap::ttm()`
cancer_map = tm_shape(ca_pts_crs) + tm_dots(size=0.5)
cancer_map
## Registered S3 method overwritten by 'jsonify':
##   method     from    
##   print.json jsonlite


Let’s play around with some of the options. We can change the color with the col= option. We can make the dots smaller by specifying the size= option. And we can change the transparency of the points with the alpha= option.

cancer_map_small = tm_shape(ca_pts_crs) + tm_dots(col = "blue", size = 0.3, alpha = 0.5)
## 
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_dots()`: use 'fill' for the fill color of polygons/symbols
## (instead of 'col'), and 'col' for the outlines (instead of 'border.col').
## [v3->v4] `tm_dots()`: use `fill_alpha` instead of `alpha`.
## This message is displayed once every 8 hours.
cancer_map_small


Let’s map the points color coded by the variable event, or whether or not the participant died over followup. We do that by specifying that the color (col=) is based on the column event. We have to specify that event is a categorical variable with style="cat".

cancer_map_events = tm_shape(ca_pts_crs) + tm_dots(size=0.3, col="event", style="cat")
## 
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_dots()`: instead of `style = "cat"`, use fill.scale =
## `tm_scale_categorical()`.
cancer_map_events


Let’s see if we can change up the color scheme.

cancer_map_events_rg = tm_shape(ca_pts_crs) + tm_dots(col = "event", palette = c("0" = "gray", "1" = "red"), style="cat")
## 
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_dots()`: instead of `style = "cat"`, use fill.scale =
## `tm_scale_categorical()`.
## ℹ Migrate the argument(s) 'palette' (rename to 'values') to
##   'tm_scale_categorical(<HERE>)'
cancer_map_events_rg
## Multiple palettes called "gray" found: "matplotlib.gray", "tableau.gray", "ocean.gray", "gmt.gray". The first one, "matplotlib.gray", is returned.


Looking good! You earned yourself a tmap badge. Now get yourself a cookie.


tmap Badge
tmap Badge


Downloading Census Data

One of the primary sources of data that we’ll be using in this class is the United States Decennial Census and the American Community Survey. There are two ways to bring Census data into R: Using and API or downloading it from an online source.

Note that we will gather ACS data from all sources. Census boundaries changed in 2020, which means that 2016-2020 and later data will not completely merge with ACS data before 2020. So make sure you merge 2020 data only with 2020 data (but you can merge 2019 data with data between 2010-2019). This is especially important for tract data, with many new tracts created in 2020 and existing tracts experiencing dramatic changes in their boundaries between 2010 and 2020. See the impact of tract boundary changes between 2000 and 2010 here. You may also explore the Neighborhood Change Database which is available through the UC Davis library, and is a dataset that incorporates tract boundary changes over time. We are working on acquiring the 2020 data there!


Download Census data from an online source

The first way to obtain Census data is to download them directly from the web onto your hard drive. There are several websites where you can download Census data including Social Explorer and PolicyMap, which we have free access to as UC Davis affiliates, and the National Historical Geographic Information System (NHGIS), which is free for everyone. To find out how to download data from PolicyMap and NHGIS, check out tutorials here and here.


Use the Census API and tidycensus

The other way to bring Census data into R is to use the Census Application Program Interface (API). An API allows for direct requests for data in machine-readable form. That is, rather than you having to navigate to some website, scroll around to find a dataset, download that dataset once you find it, save that data onto your hard drive, and then bring the data into R, you just tell R to retrieve data directly from the source using one or two lines of code.

In order to directly download data from the Census API, you need a key. You can sign up for a free key here, which you should have already done before the lab. Type your key in quotes using the census_api_key() command.

census_api_key("YOUR API KEY GOES HERE", install = TRUE)


The option install = TRUE saves the API key in your R environment, which means you don’t have to run census_api_key() every single time. The function for downloading American Community Survey (ACS) Census data is get_acs(). The command for downloading decennial Census data is get_decennial(). Both functions come from the tidycensus package, which allows users to interface with the US Census Bureau’s decennial Census and American Community Survey APIs. Getting variables using the Census API requires knowing the variable ID - and there are thousands of variables (and thus thousands of IDs) across the different Census files. To rapidly search for variables, use the commands load_variables() and View(). Because we’ll be using the ACS in this guide, let’s check the variables in the most recent 2023 5-year ACS (2019-2023) using the following commands.

acs2023 <- load_variables(2023, "acs5", cache = TRUE)
View(acs2023)


A window should have popped up showing you a record layout of the 2019-2023 ACS. To search for specific data, select “Filter” located at the top left of this window and use the search boxes that pop up. For example, type in “Hispanic” in the box under “Label”. You should see near the top of the list the first set of variables we’ll want to download - race/ethnicity. Another way of finding variable names is to search them using Social Explorer. Click on the appropriate survey data year and then “American Community Survey Tables”, which will take you to a list of variables with their Census IDs.

Let’s extract race/ethnicity data and total population for California counties using the get_acs() command.


ca <- get_acs(geography = "county", 
              year = 2023,
              variables = c(tpopr = "B03002_001", 
                            nhwhite = "B03002_003", nhblk = "B03002_004", 
                            nhasn = "B03002_006", hisp = "B03002_012"), 
              state = "CA",
              survey = "acs5",
              output = "wide")
## Getting data from the 2019-2023 5-year ACS


In the above code, we specified the following arguments

  • geography: The level of geography we want the data in; in our case, the county. Other geographic options can be found here.
  • year: The end year of the data (because we want 2016-2020, we use 2020).
  • variables: The variables we want to bring in as specified in a vector you create using the function c(). Note that we created variable names of our own (e.g. “nhwhite”) and we put the ACS IDs in quotes (“B03002_003”). Had we not done this, the variable names will come in as they are named in the ACS, which are not very descriptive.
  • state: We can filter the counties to those in a specific state. Here it is “CA” for California. If we don’t specify this, we get all counties in the United States.
  • survey: The specific Census survey were extracting data from. We want data from the 5-year American Community Survey, so we specify “acs5”. The ACS comes in 1- and 5-year - varieties.
  • output: The argument tells R to return a wide dataset as opposed to a long dataset (see this vignette for more info).

Another useful option to set is cache_table = TRUE, so you don’t have to re-download after you’ve downloaded successfully the first time. Type in ? get_acs() to see the full list of options.


As you learned in Lab 1 and Lab 2, whenever you bring in a dataset, the first thing you should always do is view it to get a sense of its structure and to make sure you got what you expected. One way of doing this is to use the glimpse() command

glimpse(ca)
## Rows: 58
## Columns: 12
## $ GEOID    <chr> "06001", "06003", "06005", "06007", "06009", "06011", "06013"…
## $ NAME     <chr> "Alameda County, California", "Alpine County, California", "A…
## $ tpoprE   <dbl> 1651949, 1695, 41029, 209470, 45995, 21895, 1161458, 27293, 1…
## $ tpoprM   <dbl> NA, 234, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
## $ nhwhiteE <dbl> 466445, 993, 30234, 139527, 35599, 6869, 455961, 16668, 14254…
## $ nhwhiteM <dbl> 1170, 215, 341, 767, 318, 133, 1843, 199, 589, 979, 75, 554, …
## $ nhblkE   <dbl> 159042, 0, 781, 3550, 529, 311, 94864, 805, 1522, 42060, 158,…
## $ nhblkM   <dbl> 1736, 14, 143, 393, 140, 44, 1594, 123, 232, 1334, 126, 271, …
## $ nhasnE   <dbl> 528377, 8, 587, 11010, 1066, 101, 212373, 821, 9640, 108809, …
## $ nhasnM   <dbl> 2269, 8, 129, 497, 238, 159, 2008, 264, 416, 1332, 182, 403, …
## $ hispE    <dbl> 385245, 249, 6361, 40829, 6403, 13639, 316799, 5350, 27230, 5…
## $ hispM    <dbl> NA, 115, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …


You get a quick, compact summary of your tibble. You can also use the head() command, which shows you the first several rows of your data object (tail() will give you the last several rows).

head(ca)
## # A tibble: 6 × 12
##   GEOID NAME  tpoprE tpoprM nhwhiteE nhwhiteM nhblkE nhblkM nhasnE nhasnM  hispE
##   <chr> <chr>  <dbl>  <dbl>    <dbl>    <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>
## 1 06001 Alam… 1.65e6     NA   466445     1170 159042   1736 528377   2269 385245
## 2 06003 Alpi… 1.69e3    234      993      215      0     14      8      8    249
## 3 06005 Amad… 4.10e4     NA    30234      341    781    143    587    129   6361
## 4 06007 Butt… 2.09e5     NA   139527      767   3550    393  11010    497  40829
## 5 06009 Cala… 4.60e4     NA    35599      318    529    140   1066    238   6403
## 6 06011 Colu… 2.19e4     NA     6869      133    311     44    101    159  13639
## # ℹ 1 more variable: hispM <dbl>


The tibble contains counties with their estimates for race/ethnicity. These variables end with the letter “E”. It also contains the margins of error for each estimate. These variables end with the letter “M”.

tidycensus is a game changer in being able to bring in Census data into R in a convenient, fast, efficient and tidy friendly way. We’ll be using this package in the next lab to bring in Census spatial data. And congratulations! You’ve just earned another badge. Fantastic!


tidycensus Badge
tidycensus Badge


Reading in data

PolicyMap

To save us time, I’ve uploaded a PolicyMap (link to .csv) on the Github for you to use in this lab. Save this file in the same folder where your Lab 2 R Markdown file resides. To read in a .csv file, first make sure that R is pointed to the folder you saved your data into. Type in getwd() to find out the current directory and setwd("directory name") to set the directory to the folder containing the data.

From a Mac laptop, I type in the following command to set the directory to the folder containing my data.

setwd("/Users/pjames1/Dropbox/UC Davis Folders/SPH 215 GIS and Public Health/Github_Website/SPH215/")


For a Windows system, you can find the pathway of a file by right clicking on it and selecting Properties. You will find that instead of a forward slash like in a Mac, a windows pathway will be indicated by a single back slash . R doesn’t like this because it thinks of a single back slash as an escape character. Use instead two back slashes \

setwd("C:\\Users\\pjames\\Documents\\UCD\\SPH215\\Labs\\Lab 3")

or a forward slash /.

setwd("C:/Users/pjames/Documents/UCD/SPH215/Labs/Lab 3")

You can also manually set the working directory by clicking on Session -> Set Working Directory -> Choose Directory from the menu.


Once you’ve set your directory, use the function read_csv(), which is a part of the tidyverse package, and plug in the name of the file in quotes inside the parentheses. Make sure you include the .csv extension.

ca.pm <- read_csv("PolicyMap Data 2025-03-27 192555 UTC.csv", skip = 1)
## Rows: 58 Columns: 10
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (8): GeoID_Description, GeoID_Name, SitsinState, GeoID, GeoID_Formatted,...
## dbl (2): mhhinc, GeoVintage
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.


The option skip = 1 tells R to skip the first row of the file when bringing it in. This is done because there are two rows of column names. The first row contains the extended version, while the second is the abridged version. Above we keep the abridged version.

You should see a tibble ca.pm pop up in your Environment window (top right). What does our data set look like?

glimpse(ca.pm)
## Rows: 58
## Columns: 10
## $ GeoID_Description <chr> "County", "County", "County", "County", "County", "C…
## $ GeoID_Name        <chr> "Alameda", "Alpine", "Amador", "Butte", "Calaveras",…
## $ SitsinState       <chr> "CA", "CA", "CA", "CA", "CA", "CA", "CA", "CA", "CA"…
## $ GeoID             <chr> "06001", "06003", "06005", "06007", "06009", "06011"…
## $ GeoID_Formatted   <chr> "=\"06001\"", "=\"06003\"", "=\"06005\"", "=\"06007\…
## $ mhhinc            <dbl> 126240, 110781, 81526, 68574, 79877, 75149, 125727, …
## $ TimeFrame         <chr> "2019-2023", "2019-2023", "2019-2023", "2019-2023", …
## $ GeoVintage        <dbl> 2022, 2022, 2022, 2022, 2022, 2022, 2022, 2022, 2022…
## $ Source            <chr> "Census", "Census", "Census", "Census", "Census", "C…
## $ Location          <chr> "California (State)", "California (State)", "Califor…


If you like viewing your data through an Excel style worksheet, type in View(ca.pm), and ca.pm should pop up in the top left window of your R Studio interface.


More data wrangling

We learned about the various data wrangling related functions from the tidyverse package in Lab 1. Let’s employ some of those functions to create a single county level dataset that joins the datasets we downloaded from the Census API and PolicyMap.

We are going to combine these datasets using the county FIPS codes. In the Census API and PolicyMap, these are contained in the variables GEOID and GeoID, respectively. Let’s make sure they are in the same class.

class(ca.pm$GeoID)
## [1] "character"
class(ca$GEOID)
## [1] "character"


Piping

One of the important innovations from the tidyverse is the pipe operator %>%. You use the pipe operator when you want to combine multiple operations into one line of continuous code. Let’s create our final data object cacounty using our brand new friend the pipe. Let’s put together a lot of what we’ve done over the past few weeks into one step! Now we are cooking with gas!

cacounty <- ca %>% 
      left_join(ca.pm, by = c("GEOID" = "GeoID")) %>%
      mutate(pwhite = nhwhiteE/tpoprE, pasian = nhasnE/tpoprE, 
              pblack = nhblkE/tpoprE, phisp = hispE/tpoprE,
             mhisp = case_when(phisp > 0.5 ~ "Majority",
                               TRUE ~ "Not Majority")) %>%
      rename(County = GeoID_Name) %>%
      select(GEOID, County, pwhite, pasian, pblack, phisp, mhisp, mhhinc)
glimpse(cacounty)
## Rows: 58
## Columns: 8
## $ GEOID  <chr> "06001", "06003", "06005", "06007", "06009", "06011", "06013", …
## $ County <chr> "Alameda", "Alpine", "Amador", "Butte", "Calaveras", "Colusa", …
## $ pwhite <dbl> 0.28236041, 0.58584071, 0.73689342, 0.66609538, 0.77397543, 0.3…
## $ pasian <dbl> 0.319850673, 0.004719764, 0.014306954, 0.052561226, 0.023176432…
## $ pblack <dbl> 0.0962753693, 0.0000000000, 0.0190353165, 0.0169475343, 0.01150…
## $ phisp  <dbl> 0.2332064, 0.1469027, 0.1550367, 0.1949157, 0.1392108, 0.622927…
## $ mhisp  <chr> "Not Majority", "Not Majority", "Not Majority", "Not Majority",…
## $ mhhinc <dbl> 126240, 110781, 81526, 68574, 79877, 75149, 125727, 66780, 1061…

Let’s break down what the pipe is doing here. First, you start out with your dataset ca. You “pipe” that into the command left_join(). Notice that you didn’t have to type in ca inside that command - %>% pipes that in for you. The command joins the data object ca.pm to ca. The result of this function gets piped into the mutate() function, which creates the percent race/ethnicity (from the Census API), and majority Hispanic variables. This gets piped into the rename() function, which renames the ambiguous variable name GeoID_Name to the more descriptive name County. This then gets piped into the final function, select(), which keeps the necessary variables. Finally, the code saves the result into cacounty which we designated at the beginning with the arrow operator.

Piping makes code clearer, and simultaneously gets rid of the need to define any intermediate objects that you would have needed to keep track of while reading the code. PIPE, Pipe, and pipe whenever you can. We need some stinkin badges!


pipe Badge!
pipe Badge!


Saving data

If you want to save your data frame or tibble as a csv file on your hard drive, use the command write_csv(). Before you save a file, make sure R is pointed to the appropriate folder on your hard drive by using the function getwd(). If it’s not pointed to the right folder, use the function setwd() to set the appropriate working directory.

write_csv(cacounty, "lab2_file.csv")

The first argument is the name of the R object you want to save. The second argument is the name of the csv file in quotes. Make sure to add the .csv extension. The file is saved in your current working directory.


Exploratory data analysis

The functions above help us bring in and clean data. The next set of functions covered in this section will help us summarize the data. Data refer to pieces of information that describe a status or a measure of magnitude. A variable is a set of observations on a particular characteristic. The distribution of a variable is a listing showing all the possible values of the data for that variable and how often they occur. Exploratory Data Analysis (EDA) encompasses a set of methods (some would say a framework or perspective) for summarizing a variable’s distribution, and the relationship between the distributions of two or more variables. We will cover two general approaches to summarizing your data: descriptive statistics and visualization via graphs and charts.


Descriptive statistics

When describing a distribution, your quantitative message is often best communicated by reducing data to a few summary numbers. These numbers are meant to summarize the “typical” value in the distribution (e.g., mean, median, mode) and the variation or “spread” in the distribution (e.g., minimum/maximum, interquartile range, standard deviation). These summary numbers are known as descriptive statistics.

We can use the function summarize() to get descriptive statistics of our data. For example, let’s calculate the mean household income in California counties. The first argument inside summarize() is the data object cacounty and the second argument is the function calculating the specific summary statistic, in this case mean().

cacounty %>%
  summarize(Mean = mean(mhhinc))
## # A tibble: 1 × 1
##     Mean
##    <dbl>
## 1 87001.

The average county median household income is $87,001. If the variable mhhinc contained missing values, we would have gotten NA as a result. To omit missing values from the calculation, you need to add rm = TRUE to mean().

We can calculate more than one summary statistic within summarize(). What is the spread of the distribution? We can add to summarize() the function sd() to calculate the standard deviation.

cacounty %>%
  summarize(Mean = mean(mhhinc), SD = sd(mhhinc))
## # A tibble: 1 × 2
##     Mean     SD
##    <dbl>  <dbl>
## 1 87001. 25547.


Does the average income differ by California region? First, let’s create a new variable region designating each county as Bay Area, Southern California, Central Valley, Capital Region and the Rest of California using the case_when() function within the mutate() function.

cacounty <- cacounty %>%
    mutate(region = case_when(County == "Sonoma" | County == "Napa" | 
                              County == "Solano" | County == "Marin" | 
                              County == "Contra Costa" | County == "San Francisco" |
                              County == "San Mateo" | County == "Alameda" | 
                              County == "Santa Clara" ~ "Bay Area",
                              County == "Imperial" | County == "Los Angeles" | 
                              County == "Orange" | County == "Riverside" |
                              County == "San Diego" | County == "San Bernardino" |
                              County == "Ventura" ~ "Southern California",
                              County == "Fresno" | County == "Madera" | 
                              County == "Mariposa" | County == "Merced" | 
                              County == "Tulare" | 
                              County == "Kings" ~ "Central Valley",
                              County == "Alpine" | County == "Colusa" |
                              County == "El Dorado" | County == "Glenn" |
                              County == "Placer" | County == "Sacramento" |
                              County == "Sutter" | County == "Yolo" |
                              County == "Yuba" ~ "Capital Region",
                              TRUE ~ "Rest"))


Next, we need to pair summarize() with the function group_by(). The function group_by() tells R to run subsequent functions on the data object by a group characteristic (such as gender, educational attainment, or in this case, region). We’ll need to use our new best friend %>% to accomplish this task.

cacounty %>%
  group_by(region) %>%
  summarize(Mean = mean(mhhinc))
## # A tibble: 5 × 2
##   region                 Mean
##   <chr>                 <dbl>
## 1 Bay Area            129297.
## 2 Capital Region       89288.
## 3 Central Valley       69265.
## 4 Rest                 74959.
## 5 Southern California  91332.

The first pipe sends cacounty into the function group_by(), which tells R to group cacounty by the variable region.

How do you know the tibble is grouped? Because it tells you!

cacounty %>%
  group_by(region) 
## # A tibble: 58 × 9
## # Groups:   region [5]
##    GEOID County       pwhite  pasian  pblack phisp mhisp        mhhinc region   
##    <chr> <chr>         <dbl>   <dbl>   <dbl> <dbl> <chr>         <dbl> <chr>    
##  1 06001 Alameda       0.282 0.320   0.0963  0.233 Not Majority 126240 Bay Area 
##  2 06003 Alpine        0.586 0.00472 0       0.147 Not Majority 110781 Capital …
##  3 06005 Amador        0.737 0.0143  0.0190  0.155 Not Majority  81526 Rest     
##  4 06007 Butte         0.666 0.0526  0.0169  0.195 Not Majority  68574 Rest     
##  5 06009 Calaveras     0.774 0.0232  0.0115  0.139 Not Majority  79877 Rest     
##  6 06011 Colusa        0.314 0.00461 0.0142  0.623 Majority      75149 Capital …
##  7 06013 Contra Costa  0.393 0.183   0.0817  0.273 Not Majority 125727 Bay Area 
##  8 06015 Del Norte     0.611 0.0301  0.0295  0.196 Not Majority  66780 Rest     
##  9 06017 El Dorado     0.741 0.0501  0.00791 0.142 Not Majority 106190 Capital …
## 10 06019 Fresno        0.270 0.108   0.0416  0.541 Majority      71434 Central …
## # ℹ 48 more rows


The second pipe takes this grouped dataset and sends it into the summarize() command, which calculates the mean income (by region, because the dataset is grouped by region).

To get the mean, median and standard deviation of median income, its correlation with percent Hispanic, and give column labels for the variables in the resulting summary table, we type in:

cacounty %>%
  group_by(region) %>%
  summarize(Mean = mean(mhhinc),
            Median = median(mhhinc),
            SD = sd(mhhinc),
            Correlation = cor(mhhinc, phisp))
## # A tibble: 5 × 5
##   region                 Mean  Median     SD Correlation
##   <chr>                 <dbl>   <dbl>  <dbl>       <dbl>
## 1 Bay Area            129297. 126240  22246.      -0.620
## 2 Capital Region       89288.  88724  17295.      -0.788
## 3 Central Valley       69265.  69120.  3918.       0.423
## 4 Rest                 74959.  71931  15803.       0.606
## 5 Southern California  91332.  89672  19132.      -0.951


The variable mhhinc is numeric. How do we summarize categorical variables? We usually summarize categorical variables by examining a frequency table. To get the percent of counties that have a majority Hispanic population mhisp, you’ll need to combine the functions group_by(), summarize() and mutate() using %>%.

cacounty %>%
  group_by(mhisp) %>%
  summarize(n = n()) %>%
  mutate(freq = n / sum(n))
## # A tibble: 2 × 3
##   mhisp            n  freq
##   <chr>        <int> <dbl>
## 1 Majority        12 0.207
## 2 Not Majority    46 0.793


The code group_by(mhisp) separates the counties by the categories of mhisp (Majority, Not Majority). We then used summarize() to count the number of counties that are Majority and Not Majority. The function to get a count is n(), and we saved this count in a variable named n. Next, we used mutate() on this table to get the proportion of counties by Majority Hispanic designation. The code sum(n) adds the values of n. We then divide the value of each n by this sum. That yields the final frequency table.

Instead of calculating descriptive statistics one at a time using summarize(), you can obtain a set of summary statistics for one or all the numeric variables in your dataset using the summary() function.

summary(cacounty)
##     GEOID              County              pwhite            pasian       
##  Length:58          Length:58          Min.   :0.09421   Min.   :0.00000  
##  Class :character   Class :character   1st Qu.:0.31535   1st Qu.:0.01888  
##  Mode  :character   Mode  :character   Median :0.49018   Median :0.04404  
##                                        Mean   :0.50723   Mean   :0.07748  
##                                        3rd Qu.:0.66599   3rd Qu.:0.08630  
##                                        Max.   :0.88832   Max.   :0.39306  
##      pblack             phisp            mhisp               mhhinc      
##  Min.   :0.000000   Min.   :0.05892   Length:58          Min.   : 53498  
##  1st Qu.:0.009634   1st Qu.:0.15611   Class :character   1st Qu.: 67888  
##  Median :0.016844   Median :0.27110   Mode  :character   Median : 80702  
##  Mean   :0.028112   Mean   :0.32170                      Mean   : 87001  
##  3rd Qu.:0.033734   3rd Qu.:0.46747                      3rd Qu.:102701  
##  Max.   :0.125967   Max.   :0.85579                      Max.   :159674  
##     region         
##  Length:58         
##  Class :character  
##  Mode  :character  
##                    
##                    
## 


Tables for presentation

The output from the descriptive statistics we’ve ran so far is not presentation ready. For example, taking a screenshot of the following results table produces unnecessary information that is confusing and messy.

cacounty %>%
  group_by(region) %>%
  summarize(Mean = mean(mhhinc),
            Median = median(mhhinc),
            SD = sd(mhhinc),
            Correlation = cor(mhhinc, phisp))
## # A tibble: 5 × 5
##   region                 Mean  Median     SD Correlation
##   <chr>                 <dbl>   <dbl>  <dbl>       <dbl>
## 1 Bay Area            129297. 126240  22246.      -0.620
## 2 Capital Region       89288.  88724  17295.      -0.788
## 3 Central Valley       69265.  69120.  3918.       0.423
## 4 Rest                 74959.  71931  15803.       0.606
## 5 Southern California  91332.  89672  19132.      -0.951

Furthermore, you would like to show a table, say, in a manuscript that does not require you to take a screenshot or copying and pasting into Excel, but instead can be produced via code, that way it can be fixed if there is an issue, and is reproducible.

One way of producing presentation tables in R is through the flextable package. First, you will need to save the tibble or data frame of results into an object. For example, let’s save the above results into an object named region.summary

region.summary <- cacounty %>%
  group_by(region) %>%
  summarize(Mean = mean(mhhinc),
            Median = median(mhhinc),
            SD = sd(mhhinc),
            Correlation = cor(mhhinc, phisp))

You then input the object into the function flextable(). Save it into an object called my_table

my_table <- flextable(region.summary)
my_table

region

Mean

Median

SD

Correlation

Bay Area

129,297.33

126,240.0

22,246.495

-0.6196837

Capital Region

89,287.78

88,724.0

17,294.860

-0.7878129

Central Valley

69,265.17

69,119.5

3,918.442

0.4231022

Rest

74,958.74

71,931.0

15,803.202

0.6064605

Southern California

91,331.86

89,672.0

19,131.821

-0.9513650


You should see a relatively clean table pop up either in your console or Viewer window.


What kind of object is my_table?

class(my_table)
## [1] "flextable"


After doing this, we can progressively pipe the my_table object through more flextable formatting functions. For example, you can change the column header names using the function set_header_labels() and center the header names using the function align().

my_table <- my_table %>%
          set_header_labels(
            region = "Region",
            Mean = "Mean",
            Median = "Median",
            SD = "Standard Deviation",
            Correlation = "Correlation") %>%
              flextable::align(align = "center", part = "all")

my_table

Region

Mean

Median

Standard Deviation

Correlation

Bay Area

129,297.33

126,240.0

22,246.495

-0.6196837

Capital Region

89,287.78

88,724.0

17,294.860

-0.7878129

Central Valley

69,265.17

69,119.5

3,918.442

0.4231022

Rest

74,958.74

71,931.0

15,803.202

0.6064605

Southern California

91,331.86

89,672.0

19,131.821

-0.9513650

Well doesn’t that look spiffy! There are a slew of options for formatting your table, including adding footnotes, borders, shade and other features. Check out this useful tutorial for an explanation of some of these features.

Once you’re done formatting your table, you can then export it to Word, PowerPoint or HTML or as an image (PNG) files. To do this, use one of the following functions: save_as_docx(), save_as_pptx(), save_as_image(), and save_as_html().

Use the save_as_image() function to save your table as an image.

save_as_image(my_table, path = "reg_income.png")
## [1] "reg_income.png"

You first put in the table my_table, and set the file name with the .png extension. Check your working directory. You should see the file reg_income.png.


Data visualization

Another way of summarizing variables and their relationships is through graphs and charts. The main package for R graphing is ggplot2 which is a part of the tidyverse package. The graphing function is ggplot() and it takes on the basic template

ggplot(data = <DATA>) +
      <GEOM_FUNCTION>(mapping = aes(x, y)) +
      <OPTIONS>()


  1. ggplot() is the base function where you specify your dataset using the data = argument.
  2. You then need to build on this base by using the plus operator + and () where () is a unique geom function indicating the type of graph you want to plot. Each unique function has its unique set of mapping arguments which you specify using the mapping = aes() argument. Charts and graphs have an x-axis, y-axis, or both. Check this ggplot cheat sheet for all possible geoms.
  3. <OPTIONS>() are a set of functions you can specify to change the look of the graph, for example relabeling the axes or adding a title.

The basic idea is that a ggplot graphic layers geometric objects (circles, lines, etc), themes, and scales on top of data.

You first start out with the base layer. It represents the empty ggplot layer defined by the ggplot() function.

cacounty %>%
  ggplot()

We get an empty plot. We haven’t told ggplot() what type of geometric object(s) we want to plot, nor how the variables should be mapped to the geometric objects, so we just have a blank plot. We have geoms to paint the blank canvas.

From here, we add a “geom” layer to the ggplot object. Layers are added to ggplot objects using +, instead of %>%, since you are not explicitly piping an object into each subsequent layer, but adding layers on top of one another. Each geom is associated with a specific type of graph.

Let’s go through some of the more common and popular graphs for visualizing your data.


Histogram

A typical visual for summarizing a single numeric variable is a histogram. To create a histogram, use geom_histogram() for <GEOM_FUNCTION()>. Let’s create a histogram of median household income. Note that we don’t need to specify the y= here because we are plotting only one variable. We pipe in the object cacounty into ggplot() to establish the base layer. We then use geom_histogram() to add the data layer on top of the base.

cacounty %>%
  ggplot() + 
  geom_histogram(mapping = aes(x=mhhinc)) 
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

We can continue to add layers to the plot. For example, we use the argument xlab("Median household income") to label the x-axis as “Median household income”.

cacounty %>%
  ggplot() + 
  geom_histogram(mapping = aes(x=mhhinc)) +
  xlab("Median household income")
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

Note the message produced with the plot. It tells us that we can use the bins = argument to change the number of bins used to produce the histogram. You can increase the number of bins to make the bins narrower and thus get a finer grain of detail. Or you can decrease the number of bins to get a broader visual summary of the shape of the variable’s distribution. Compare bins = 10 to bins = 50.


cacounty %>%
  ggplot() + 
  geom_histogram(mapping = aes(x=mhhinc), bins=10) +
  xlab("Median household income")


Boxplot

We can use a boxplot to visually summarize the distribution of a single numeric variable or the relationship between a categorical and numeric variable. Use geom_boxplot() for <GEOM_FUNCTION()> to create a boxplot. Let’s examine median household income. Note that a boxplot uses y= rather than x= to specify where mhhinc goes. We also provide a descriptive y-axis label using the ylab() function.

cacounty %>%
  ggplot() +
    geom_boxplot(mapping = aes(y = mhhinc)) +
    ylab("Median household income")


Let’s examine the distribution of median income by mhisp. Because we are examining the association between two variables, we need to specify x and y variables in aes() (we also specify both x- and y-axis labels).

cacounty %>%
  ggplot() +
    geom_boxplot(mapping = aes(x = mhisp, y = mhhinc)) +
    xlab("Majority Hispanic") +
    ylab("Median household income")

The top and bottom of a boxplot represent the 75th and 25th percentiles, respectively. The line in the middle of the box is the 50th percentile. Points outside the whiskers represent outliers. Outliers are defined as having values that are either larger than the 75th percentile plus 1.5 times the IQR or smaller than the 25th percentile minus 1.5 times the IQR.


The boxplot is for all counties combined. Use the facet_wrap() function to separate by region. Notice the tilde ~ before the variable region inside facet_wrap().

cacounty %>%
  ggplot() +
    geom_boxplot(mapping = aes(x = mhisp, y = mhhinc)) +
    xlab("Majority Hispanic") +
    ylab("Median household income") +
    facet_wrap(~region) 


Bar chart

The primary purpose of a bar chart is to illustrate and compare the values for a categorical variable. Bar charts show either the number or frequency of each category. To create a bar chart, use geom_bar() for (). Let’s show a bar chart of median household income by region. We’ll borrow from code above that generated a tibble of mean household income by region, and pipe that into ggplot().

cacounty %>%
  group_by(region) %>%
  summarize(Mean = mean(mhhinc)) %>%
  ggplot(aes(x=region, y = Mean)) +
  geom_bar(stat = "Identity") +
  xlab("Region") +
  ylab("Average household income")


Right now the bars are ordered based on the region names. We can order the bars in descending order based on household income by using the reorder() function. Notice the negative sign in front of Mean to order by descending order.

cacounty %>%
  group_by(region) %>%
  summarize(Mean = mean(mhhinc)) %>%
  ggplot(aes(x=reorder(region, -Mean), y = Mean)) +
  geom_bar(stat = "Identity") +
  xlab("Region") +
  ylab("Average household income")


We can flip the axes using the function coord_flip().

cacounty %>%
  group_by(region) %>%
  summarize(Mean = mean(mhhinc)) %>%
  ggplot(aes(x=reorder(region, -Mean), y = Mean)) +
  geom_bar(stat = "Identity") +
  xlab("Region") +
  ylab("Average household income") +
  coord_flip()


Well aren’t we fancy? ggplot() is a powerful function, and you can make a lot of visually captivating graphs. We have just scratched the surface of its functions and features. You can also make your graphs really “pretty” and professional looking by altering graphing features, including colors, labels, titles and axes. For a list of ggplot() functions that alter various features of a graph, check out Chapter 28 in RDS.

Here’s your ggplot2 badge. Wear it with pride! OK, we’ve done a lot today. Get outside and get some fresh air!


ggplot2 Badge
ggplot2 Badge


Other US Government datasets

Check out the Data Sources link for more links to US Government Data


Acknowledgements

Major acknowledgements to Noli Brazil (as always) and Crime by the Numbers.

Lab 4.

More with vector data and introducing rasters

In this lab, we are going to work more with vector data. We will learn how to visualize Census data, we will talk about how to wrangle spatial data, and we will get into some really cool ways to create choropleth maps (maps color coded by attributes). Finally, we will introduce the concept of rasters.

The objectives of this guide are to teach you:

  1. Visualize Vector Data
  2. Process Vector Data
  3. Create Publication-Ready Maps
  4. Introduce Rasters

Let’s get cracking!

First, let’s install our packages.


library(sf)
library(MapGAM)
library(tidyverse)
library(tidycensus)
library(flextable)
library(RColorBrewer)
library(tmap)
library(terra)
## terra 1.8.29
## 
## Attaching package: 'terra'
## The following objects are masked from 'package:flextable':
## 
##     align, colorize, rotate, width
## The following object is masked from 'package:tidyr':
## 
##     extract


In Lab 2, we worked with the tidycensus package and the Census API to bring in Census data into R. We can use the same commands to bring in Census geography. If you haven’t already, make sure to sign up for and install your Census API key. If you could not install your API key, you’ll need to use census_api_key() to activate it with the following code:

census_api_key("YOUR API KEY GOES HERE", install = TRUE)


Use the set_acs() command to bring in California tract-level race/ethnicity counts, total population, and total number of households. How did I find the variable IDs? Check Lab 2. Since we want tracts, we’ll use the geography = "tract" argument.

ca.tracts <- get_acs(geography = "tract", 
              year = 2023,
              variables = c(tpopr = "B03002_001", 
                            nhwhite = "B03002_003", nhblk = "B03002_004", 
                            nhasn = "B03002_006", hisp = "B03002_012"), 
              state = "CA",
              output = "wide",
              survey = "acs5",
              geometry = TRUE,
              cb = FALSE)
##   |                                                                              |                                                                      |   0%  |                                                                              |                                                                      |   1%  |                                                                              |=                                                                     |   1%  |                                                                              |=                                                                     |   2%  |                                                                              |==                                                                    |   2%  |                                                                              |==                                                                    |   3%  |                                                                              |==                                                                    |   4%  |                                                                              |===                                                                   |   4%  |                                                                              |===                                                                   |   5%  |                                                                              |====                                                                  |   5%  |                                                                              |====                                                                  |   6%  |                                                                              |=====                                                                 |   6%  |                                                                              |=====                                                                 |   7%  |                                                                              |=====                                                                 |   8%  |                                                                              |======                                                                |   8%  |                                                                              |======                                                                |   9%  |                                                                              |=======                                                               |   9%  |                                                                              |=======                                                               |  10%  |                                                                              |=======                                                               |  11%  |                                                                              |========                                                              |  11%  |                                                                              |========                                                              |  12%  |                                                                              |=========                                                             |  12%  |                                                                              |=========                                                             |  13%  |                                                                              |=========                                                             |  14%  |                                                                              |==========                                                            |  14%  |                                                                              |==========                                                            |  15%  |                                                                              |===========                                                           |  15%  |                                                                              |===========                                                           |  16%  |                                                                              |============                                                          |  16%  |                                                                              |============                                                          |  17%  |                                                                              |============                                                          |  18%  |                                                                              |=============                                                         |  18%  |                                                                              |=============                                                         |  19%  |                                                                              |==============                                                        |  19%  |                                                                              |==============                                                        |  20%  |                                                                              |==============                                                        |  21%  |                                                                              |===============                                                       |  21%  |                                                                              |===============                                                       |  22%  |                                                                              |================                                                      |  22%  |                                                                              |================                                                      |  23%  |                                                                              |================                                                      |  24%  |                                                                              |=================                                                     |  24%  |                                                                              |=================                                                     |  25%  |                                                                              |==================                                                    |  25%  |                                                                              |==================                                                    |  26%  |                                                                              |===================                                                   |  26%  |                                                                              |===================                                                   |  27%  |                                                                              |===================                                                   |  28%  |                                                                              |====================                                                  |  28%  |                                                                              |====================                                                  |  29%  |                                                                              |=====================                                                 |  29%  |                                                                              |=====================                                                 |  30%  |                                                                              |=====================                                                 |  31%  |                                                                              |======================                                                |  31%  |                                                                              |======================                                                |  32%  |                                                                              |=======================                                               |  32%  |                                                                              |=======================                                               |  33%  |                                                                              |=======================                                               |  34%  |                                                                              |========================                                              |  34%  |                                                                              |========================                                              |  35%  |                                                                              |=========================                                             |  35%  |                                                                              |=========================                                             |  36%  |                                                                              |==========================                                            |  36%  |                                                                              |==========================                                            |  37%  |                                                                              |==========================                                            |  38%  |                                                                              |===========================                                           |  38%  |                                                                              |===========================                                           |  39%  |                                                                              |============================                                          |  39%  |                                                                              |============================                                          |  40%  |                                                                              |============================                                          |  41%  |                                                                              |=============================                                         |  41%  |                                                                              |=============================                                         |  42%  |                                                                              |==============================                                        |  42%  |                                                                              |==============================                                        |  43%  |                                                                              |==============================                                        |  44%  |                                                                              |===============================                                       |  44%  |                                                                              |===============================                                       |  45%  |                                                                              |================================                                      |  45%  |                                                                              |================================                                      |  46%  |                                                                              |=================================                                     |  46%  |                                                                              |=================================                                     |  47%  |                                                                              |=================================                                     |  48%  |                                                                              |==================================                                    |  48%  |                                                                              |==================================                                    |  49%  |                                                                              |===================================                                   |  49%  |                                                                              |===================================                                   |  50%  |                                                                              |===================================                                   |  51%  |                                                                              |====================================                                  |  51%  |                                                                              |====================================                                  |  52%  |                                                                              |=====================================                                 |  52%  |                                                                              |=====================================                                 |  53%  |                                                                              |=====================================                                 |  54%  |                                                                              |======================================                                |  54%  |                                                                              |======================================                                |  55%  |                                                                              |=======================================                               |  55%  |                                                                              |=======================================                               |  56%  |                                                                              |========================================                              |  56%  |                                                                              |========================================                              |  57%  |                                                                              |========================================                              |  58%  |                                                                              |=========================================                             |  58%  |                                                                              |=========================================                             |  59%  |                                                                              |==========================================                            |  59%  |                                                                              |==========================================                            |  60%  |                                                                              |==========================================                            |  61%  |                                                                              |===========================================                           |  61%  |                                                                              |===========================================                           |  62%  |                                                                              |============================================                          |  62%  |                                                                              |============================================                          |  63%  |                                                                              |============================================                          |  64%  |                                                                              |=============================================                         |  64%  |                                                                              |=============================================                         |  65%  |                                                                              |==============================================                        |  65%  |                                                                              |==============================================                        |  66%  |                                                                              |===============================================                       |  66%  |                                                                              |===============================================                       |  67%  |                                                                              |===============================================                       |  68%  |                                                                              |================================================                      |  68%  |                                                                              |================================================                      |  69%  |                                                                              |=================================================                     |  69%  |                                                                              |=================================================                     |  70%  |                                                                              |=================================================                     |  71%  |                                                                              |==================================================                    |  71%  |                                                                              |==================================================                    |  72%  |                                                                              |===================================================                   |  72%  |                                                                              |===================================================                   |  73%  |                                                                              |===================================================                   |  74%  |                                                                              |====================================================                  |  74%  |                                                                              |====================================================                  |  75%  |                                                                              |=====================================================                 |  75%  |                                                                              |=====================================================                 |  76%  |                                                                              |======================================================                |  76%  |                                                                              |======================================================                |  77%  |                                                                              |======================================================                |  78%  |                                                                              |=======================================================               |  78%  |                                                                              |=======================================================               |  79%  |                                                                              |========================================================              |  79%  |                                                                              |========================================================              |  80%  |                                                                              |========================================================              |  81%  |                                                                              |=========================================================             |  81%  |                                                                              |=========================================================             |  82%  |                                                                              |==========================================================            |  82%  |                                                                              |==========================================================            |  83%  |                                                                              |==========================================================            |  84%  |                                                                              |===========================================================           |  84%  |                                                                              |===========================================================           |  85%  |                                                                              |============================================================          |  85%  |                                                                              |============================================================          |  86%  |                                                                              |=============================================================         |  86%  |                                                                              |=============================================================         |  87%  |                                                                              |=============================================================         |  88%  |                                                                              |==============================================================        |  88%  |                                                                              |==============================================================        |  89%  |                                                                              |===============================================================       |  89%  |                                                                              |===============================================================       |  90%  |                                                                              |===============================================================       |  91%  |                                                                              |================================================================      |  91%  |                                                                              |================================================================      |  92%  |                                                                              |=================================================================     |  92%  |                                                                              |=================================================================     |  93%  |                                                                              |=================================================================     |  94%  |                                                                              |==================================================================    |  94%  |                                                                              |==================================================================    |  95%  |                                                                              |===================================================================   |  95%  |                                                                              |===================================================================   |  96%  |                                                                              |====================================================================  |  96%  |                                                                              |====================================================================  |  97%  |                                                                              |====================================================================  |  98%  |                                                                              |===================================================================== |  98%  |                                                                              |===================================================================== |  99%  |                                                                              |======================================================================|  99%  |                                                                              |======================================================================| 100%


The only difference between the code above and what we used in Lab 2 is we have one additional argument added to the get_acs() command: geometry = TRUE. This tells R to bring in the spatial features associated with the geography you specified in the command, in the above case California tracts. You can set cache_table = TRUE so that you don’t have to re-download after you’ve downloaded successfully the first time. This is important because you might be downloading a really large file, or may encounter Census FTP issues when trying to collect data.


Note: We can also download the data another way. We can go to the Census Shapefiles website and navigate to 2023, Census Tracts, then California. We can then download a .zip file that contains an ESRI shapefile of the Census tracts for California. When we unzip the file, we see a series of files. Thankfully, the sf package has an st_read() function that can tackle this! For more detailed data downloads, you can use National Historical Geographic Information System (NHGIS). The code below is example of how we might bring in a shapefile, just for future reference!

ca.tracts <- st_read("/Users/pjames1/Downloads/tl_2024_06_tract/tl_2024_06_tract.shp")

OK, let’s go back to the data we got from tidycensus. Lets take a look at our data.


ca.tracts
## Simple feature collection with 9129 features and 12 fields
## Geometry type: MULTIPOLYGON
## Dimension:     XY
## Bounding box:  xmin: -124.482 ymin: 32.52951 xmax: -114.1312 ymax: 42.0095
## Geodetic CRS:  NAD83
## First 10 features:
##          GEOID                                                 NAME tpoprE
## 1  06001442700        Census Tract 4427; Alameda County; California   2998
## 2  06001442800        Census Tract 4428; Alameda County; California   3087
## 3  06037204920 Census Tract 2049.20; Los Angeles County; California   2459
## 4  06037205110 Census Tract 2051.10; Los Angeles County; California   3690
## 5  06037320101 Census Tract 3201.01; Los Angeles County; California   3402
## 6  06037205120 Census Tract 2051.20; Los Angeles County; California   3409
## 7  06037206010 Census Tract 2060.10; Los Angeles County; California   3490
## 8  06037206020 Census Tract 2060.20; Los Angeles County; California   8179
## 9  06037206050 Census Tract 2060.50; Los Angeles County; California   2290
## 10 06037111402 Census Tract 1114.02; Los Angeles County; California   5998
##    tpoprM nhwhiteE nhwhiteM nhblkE nhblkM nhasnE nhasnM hispE hispM
## 1     475      921      168    119    115   1577    469   308    81
## 2     386      779      183     12     49   1671    356   561   181
## 3     387       68       33     49     51      0     14  2331   387
## 4     446       14       14      8     12      0     14  3642   446
## 5     438      236      131     65     93    123    164  2978   481
## 6     624       15       24      8     10     70    104  3303   628
## 7     605      500      264     17     75   1030    235  1919   519
## 8     350     1379      165   2633    344    238     76  3541   285
## 9     509      138       49     18     22    151     82  1932   519
## 10   1024     2685      831     18     19    920    563  2322   433
##                          geometry
## 1  MULTIPOLYGON (((-122.0172 3...
## 2  MULTIPOLYGON (((-122.0023 3...
## 3  MULTIPOLYGON (((-118.2028 3...
## 4  MULTIPOLYGON (((-118.2196 3...
## 5  MULTIPOLYGON (((-118.4388 3...
## 6  MULTIPOLYGON (((-118.2202 3...
## 7  MULTIPOLYGON (((-118.2392 3...
## 8  MULTIPOLYGON (((-118.2379 3...
## 9  MULTIPOLYGON (((-118.2287 3...
## 10 MULTIPOLYGON (((-118.5023 3...


The object looks much like a basic tibble, but with a few differences.

  • You’ll find that the description of the object now indicates that it is a simple feature collection with 9,129 features (tracts in California) with 13 fields (attributes or columns of data).
  • The Geometry Type indicates that the spatial data are in MULTIPOLYGON form (as opposed to points or lines, the other basic vector data forms).
  • Bounding box indicates the spatial extent of the features (from left to right, for example, California tracts go from a longitude of -124.482 to -114.1312).
  • Geodetic CRS tells us the coordinate reference system.
  • The final difference is that the data frame contains the column geometry. This column (a list-column) contains the geometry for each observation. This looks familiar!

At its most basic, an sf object is a collection of simple features that includes attributes and geometries in the form of a data frame. In other words, it is a data frame (or tibble) with rows of features, columns of attributes, and a special column always named geometry that contains the spatial aspects of the features.

If you want to peek behind the curtain and learn more about the nitty gritty details about simple features, check out the official sf vignette.


Data Wrangling

There is a lot of stuff behind the curtain of how R handles spatial data as simple features, but the main takeaway is that sf objects are data frames. This means you can use many of the tidyverse functions we’ve learned in the past couple labs to manipulate sf objects, including the pipe %>% operator. For example, let’s break up the column NAME into separate tract, county and state variables using the separate() function

We do all of this in one line of continuous code using the pipe operator %>%

ca.tracts <- ca.tracts %>%
              separate(NAME, c("Tract", "County", "State"), sep = "; ")
glimpse(ca.tracts)
## Rows: 9,129
## Columns: 15
## $ GEOID    <chr> "06001442700", "06001442800", "06037204920", "06037205110", "…
## $ Tract    <chr> "Census Tract 4427", "Census Tract 4428", "Census Tract 2049.…
## $ County   <chr> "Alameda County", "Alameda County", "Los Angeles County", "Lo…
## $ State    <chr> "California", "California", "California", "California", "Cali…
## $ tpoprE   <dbl> 2998, 3087, 2459, 3690, 3402, 3409, 3490, 8179, 2290, 5998, 4…
## $ tpoprM   <dbl> 475, 386, 387, 446, 438, 624, 605, 350, 509, 1024, 578, 621, …
## $ nhwhiteE <dbl> 921, 779, 68, 14, 236, 15, 500, 1379, 138, 2685, 2306, 11, 81…
## $ nhwhiteM <dbl> 168, 183, 33, 14, 131, 24, 264, 165, 49, 831, 535, 27, 73, 18…
## $ nhblkE   <dbl> 119, 12, 49, 8, 65, 8, 17, 2633, 18, 18, 93, 26, 2, 352, 1086…
## $ nhblkM   <dbl> 115, 49, 51, 12, 93, 10, 75, 344, 22, 19, 97, 41, 4, 264, 679…
## $ nhasnE   <dbl> 1577, 1671, 0, 0, 123, 70, 1030, 238, 151, 920, 665, 13, 29, …
## $ nhasnM   <dbl> 469, 356, 14, 14, 164, 104, 235, 76, 82, 563, 176, 21, 45, 26…
## $ hispE    <dbl> 308, 561, 2331, 3642, 2978, 3303, 1919, 3541, 1932, 2322, 134…
## $ hispM    <dbl> 81, 181, 387, 446, 481, 628, 519, 285, 519, 433, 265, 618, 18…
## $ geometry <MULTIPOLYGON [°]> MULTIPOLYGON (((-122.0172 3..., MULTIPOLYGON (((…


Another important data wrangling operation is to join attribute data to an sf object. For example, let’s say you wanted to add tract level median household income, which is located in the file ca_med_inc_2018.csv. Read the file in.

ca.inc <- get_acs(geography = "tract", 
              year = 2023,
              variables = c(medinc = "B19013_001"), 
              state = "CA",
              survey = "acs5",
              output = "wide")
## Getting data from the 2019-2023 5-year ACS


Unlike before, we brought these data in without the geometry = TRUE option. So this is just a table. But remember, an sf object is a data frame, so we can use left_join(), which we covered in Lab 1, to join the files ca.inc and ca.tracts.

ca.tracts <- ca.tracts %>%
  left_join(ca.inc, by = "GEOID")

#take a look to make sure the join worked
glimpse(ca.tracts)
## Rows: 9,129
## Columns: 18
## $ GEOID    <chr> "06001442700", "06001442800", "06037204920", "06037205110", "…
## $ Tract    <chr> "Census Tract 4427", "Census Tract 4428", "Census Tract 2049.…
## $ County   <chr> "Alameda County", "Alameda County", "Los Angeles County", "Lo…
## $ State    <chr> "California", "California", "California", "California", "Cali…
## $ tpoprE   <dbl> 2998, 3087, 2459, 3690, 3402, 3409, 3490, 8179, 2290, 5998, 4…
## $ tpoprM   <dbl> 475, 386, 387, 446, 438, 624, 605, 350, 509, 1024, 578, 621, …
## $ nhwhiteE <dbl> 921, 779, 68, 14, 236, 15, 500, 1379, 138, 2685, 2306, 11, 81…
## $ nhwhiteM <dbl> 168, 183, 33, 14, 131, 24, 264, 165, 49, 831, 535, 27, 73, 18…
## $ nhblkE   <dbl> 119, 12, 49, 8, 65, 8, 17, 2633, 18, 18, 93, 26, 2, 352, 1086…
## $ nhblkM   <dbl> 115, 49, 51, 12, 93, 10, 75, 344, 22, 19, 97, 41, 4, 264, 679…
## $ nhasnE   <dbl> 1577, 1671, 0, 0, 123, 70, 1030, 238, 151, 920, 665, 13, 29, …
## $ nhasnM   <dbl> 469, 356, 14, 14, 164, 104, 235, 76, 82, 563, 176, 21, 45, 26…
## $ hispE    <dbl> 308, 561, 2331, 3642, 2978, 3303, 1919, 3541, 1932, 2322, 134…
## $ hispM    <dbl> 81, 181, 387, 446, 481, 628, 519, 285, 519, 433, 265, 618, 18…
## $ NAME     <chr> "Census Tract 4427; Alameda County; California", "Census Trac…
## $ medincE  <dbl> 199154, 180800, 70500, 52262, 110967, 28516, 60703, 148661, 4…
## $ medincM  <dbl> 30525, 28293, 15698, 5659, 22149, 3246, 28578, 39223, 19295, …
## $ geometry <MULTIPOLYGON [°]> MULTIPOLYGON (((-122.0172 3..., MULTIPOLYGON (((…


Note that we can’t use left_join() to join the attribute tables of two sf files. You will need to either make one of them not spatial by using the st_drop_geometry() function or use the st_join() function to spatially join them.

We use the function tm_shape() from the tmap package to map the data.

tmap_mode("plot")
## ℹ tmap modes "plot" - "view"
tract_map <- tm_shape(ca.tracts) +   tm_polygons()
tract_map


Spatial Data Wrangling

There is Data Wrangling and then there is Spatial Data Wrangling. Cue dangerous sounding music. Well, it’s not that dangerous or scary. Spatial Data Wrangling involves cleaning or altering your data set based on the geographic location of features. The sf package offers a suite of functions unique to wrangling spatial data. Most of these functions start out with the prefix st_. To see all of the functions, type in

methods(class = "sf")


We won’t go through all of these functions as the list is quite extensive. But, we’ll go through a few relevant spatial operations for this class below. The function we will be primarily using is st_join().


Intersect

A common spatial data wrangling issue is to subset a set of spatial objects based on their location relative to another spatial object. In our case, we want to keep California tracts that are in the Sacramento metro area. We can do this using the st_join() function. We’ll need to specify a type of join. Let’s first try join = st_intersects. First, let’s bring in a polygon of the Sacramento metro area from Github.

url <- "https://github.com/pjames-ucdavis/SPH215/raw/main/sac.metro.rds"
download.file(url, destfile = "sac.metro.rds", mode = "wb")
sac.metro <- readRDS("sac.metro.rds")


Let’s take a look at sac.metro and understand what file it is.

glimpse(sac.metro)
## Rows: 1
## Columns: 14
## $ CSAFP    <chr> "472"
## $ CBSAFP   <chr> "40900"
## $ GEOID    <chr> "40900"
## $ GEOIDFQ  <chr> "310M700US40900"
## $ NAME     <chr> "Sacramento-Roseville-Folsom, CA"
## $ NAMELSAD <chr> "Sacramento-Roseville-Folsom, CA Metro Area"
## $ LSAD     <chr> "M1"
## $ MEMI     <chr> "1"
## $ MTFCC    <chr> "G3110"
## $ ALAND    <dbl> 13191309279
## $ AWATER   <dbl> 548018355
## $ INTPTLAT <chr> "+38.7902715"
## $ INTPTLON <chr> "-121.0056427"
## $ geometry <MULTIPOLYGON [°]> MULTIPOLYGON (((-120.0064 3...


OK, that geometry looks good. And it’s a polygon, so that’s good. Let’s now try to intersect our sac.metro dataset with our ca.tracts dataset.

sac.metro.tracts.int <- st_join(ca.tracts, sac.metro, 
                                join = st_intersects, left=FALSE)


The above code tells R to identify the polygons in ca.tracts that intersect with the polygon sac.metro. We indicate we want a polygon intersection by specifying join = st_intersects. The option left=FALSE tells R to remove the polygons from ca.tracts that do not intersect (make it TRUE and see what happens) with sac.metro. Plotting our tracts, we get:

tm_shape(sac.metro.tracts.int) +
  tm_polygons(col = "blue") +
tm_shape(sac.metro) +  
  tm_borders(col = "red")


Within

We have one small issue. Using join = st_intersects returns all tracts that intersect sac.metro, which include those that touch the metro’s boundary. No bueno. We can instead use the argument join = st_within to return tracts that are completely within the metro area.

sac.metro.tracts.w <- st_join(ca.tracts, sac.metro, join = st_within, left=FALSE)

tm_shape(sac.metro.tracts.w) +
  tm_polygons(col = "blue") +
tm_shape(sac.metro) +
  tm_borders(col = "red")


Looking much better! Now, if we look at sac.metro.tracts.w’s attribute table, you’ll see it includes all the variables from both ca.tracts and sac.metro. We don’t need these variables, so use select() to eliminate them. You’ll also notice that if variables from two data sets share the same name, R will keep both and attach a .x and .y to the end. For example, I was found in both ca.tracts and sac.metro, so R named one GEOID.x and the other that was merged in was named GEOID.y.


Mapping in R

ggplot

OK, so now we’ve talked a little about how to bring in and manipulate vector polygon data, let’s do some mapping and create some choropleth maps. We can do this with the ggplot package, the tmap package, and the leaflet package (which we won’t cover now, but it’s very cool for interactive maps). Let’s start with ggplot.


ggplot

Because sf is tidy friendly, it is no surprise we can use the tidyverse plotting function ggplot() to make maps. We already received an introduction to ggplot() in Lab 2. Recall its basic structure:

ggplot(data = <DATA>) +
      <GEOM_FUNCTION>(mapping = aes(x, y)) +
      <OPTIONS>()


In mapping, geom_sf() is <GEOM_FUNCTION>(). Unlike with functions like geom_histogram() and geom_boxplot(), we don’t specify an x and y axis. Instead you use fill if you want to map a variable or color to just map boundaries.

Let’s use ggplot() to make a choropleth map. We need to specify a numeric variable in the fill = argument within geom_sf(). Here we map tract-level median household income in the Sacramento metro area.

ggplot(data = sac.metro.tracts.w) +
  geom_sf(aes(fill = medincE))


We can also specify a title (as well as subtitles and captions) using the labs() function.

ggplot(data = sac.metro.tracts.w) +
  geom_sf(aes(fill = medincE)) +
    labs(title = "Median Income Sacramento MSA Tracts") 


We can make further layout adjustments to the map. Don’t like a blue scale on the legend? You can change it using the scale_file_gradient() function. Let’s change it to a white to red gradient. We can also eliminate the gray tract border colors to make the fill color distinction clearer. We do this by specifying color = NA inside geom_sf(). We can also get rid of the gray background by specifying a basic black and white theme using theme_bw(). We also added a caption indicating the source of the data using the captions = parameter within labs(). We then changed the color to red using labels for low= and high=, and we added a name to our legend with `name=’.

ggplot(data = sac.metro.tracts.w) +
  geom_sf(aes(fill = medincE), color = NA) +
    scale_fill_gradient(low= "white", high = "red", na.value ="gray", name = "Median Income") +  
    labs(title = "Median Income Sacramento MSA Tracts",
         caption = "Source: American Community Survey") +  
  theme_bw()


Dare I say, we are ready for the New York Times with this map!


Points on top of polygons

OK, now that we have mapped points and mapped polygons, let’s put them both together! First, we are going to bring in our old friend CAdata from last week’s lab.

data(CAdata)
ca_pts <- CAdata
summary(ca_pts)
##       time               event              X                 Y          
##  Min.   : 0.004068   Min.   :0.0000   Min.   :1811375   Min.   :-241999  
##  1st Qu.: 1.931247   1st Qu.:0.0000   1st Qu.:2018363   1st Qu.: -94700  
##  Median : 4.749980   Median :1.0000   Median :2325084   Median : -60386  
##  Mean   : 6.496130   Mean   :0.6062   Mean   :2230219   Mean   :  87591  
##  3rd Qu.: 9.609031   3rd Qu.:1.0000   3rd Qu.:2380230   3rd Qu.: 318280  
##  Max.   :24.997764   Max.   :1.0000   Max.   :2705633   Max.   : 770658  
##       AGE         INS      
##  Min.   :25.00   Mcd: 431  
##  1st Qu.:53.00   Mcr:1419  
##  Median :62.00   Mng:2304  
##  Mean   :61.28   Oth: 526  
##  3rd Qu.:71.00   Uni: 168  
##  Max.   :80.00   Unk: 152
ca_pts <- st_as_sf(CAdata, coords=c("X","Y"))
ca_proj <- "+proj=lcc +lat_1=40 +lat_2=41.66666666666666 
             +lat_0=39.33333333333334 +lon_0=-122 +x_0=2000000 
             +y_0=500000.0000000002 +ellps=GRS80 
             +datum=NAD83 +units=m +no_defs"

#Set CRS
ca_pts_crs <- st_set_crs(ca_pts, ca_proj)


This time, we will map those points with ggplot.

ggplot(data = ca_pts_crs) +
  geom_sf(fill = "black") +
  labs(title = "Study Participants",
       caption = "Source: Ovarian Cancer Cases") +  
  theme_bw()


We can overlay the points over Sacramento tracts to give the locations some perspective. Here, you add two geom_sf() arguments for the tracts and the cancer cases.

ggplot() +
  geom_sf(data = sac.metro.tracts.w) +
  geom_sf(data = ca_pts_crs, fill = "black") +
  labs(title = "Study Participants",
       caption = "Source: Ovarian Cancer Cases") +  
  theme_bw()


Hmmm. That doesn’t look great. We have lots of cases outside of Sacramento. Let’s filter out to just pick cases within the Sacramento area.

ca_pts_crs.w <- st_join(ca_pts_crs, sac.metro.tracts.w, join = st_within, left=FALSE)

Ooof. That doesn’t work. It says our st_crs(x) == st_crs(y) is not TRUE. That means our Coordinate Reference Systems are not matching! Let’s transform our cancer dataset ca_pts_crs.w to match the CRS for sac.metro.tracts.w with one easy step using st_transform:

#check crs of each dataset
st_crs(ca_pts_crs)
## Coordinate Reference System:
##   User input: +proj=lcc +lat_1=40 +lat_2=41.66666666666666 
##              +lat_0=39.33333333333334 +lon_0=-122 +x_0=2000000 
##              +y_0=500000.0000000002 +ellps=GRS80 
##              +datum=NAD83 +units=m +no_defs 
##   wkt:
## PROJCRS["unknown",
##     BASEGEOGCRS["unknown",
##         DATUM["North American Datum 1983",
##             ELLIPSOID["GRS 1980",6378137,298.257222101,
##                 LENGTHUNIT["metre",1]],
##             ID["EPSG",6269]],
##         PRIMEM["Greenwich",0,
##             ANGLEUNIT["degree",0.0174532925199433],
##             ID["EPSG",8901]]],
##     CONVERSION["unknown",
##         METHOD["Lambert Conic Conformal (2SP)",
##             ID["EPSG",9802]],
##         PARAMETER["Latitude of false origin",39.3333333333333,
##             ANGLEUNIT["degree",0.0174532925199433],
##             ID["EPSG",8821]],
##         PARAMETER["Longitude of false origin",-122,
##             ANGLEUNIT["degree",0.0174532925199433],
##             ID["EPSG",8822]],
##         PARAMETER["Latitude of 1st standard parallel",40,
##             ANGLEUNIT["degree",0.0174532925199433],
##             ID["EPSG",8823]],
##         PARAMETER["Latitude of 2nd standard parallel",41.6666666666667,
##             ANGLEUNIT["degree",0.0174532925199433],
##             ID["EPSG",8824]],
##         PARAMETER["Easting at false origin",2000000,
##             LENGTHUNIT["metre",1],
##             ID["EPSG",8826]],
##         PARAMETER["Northing at false origin",500000,
##             LENGTHUNIT["metre",1],
##             ID["EPSG",8827]]],
##     CS[Cartesian,2],
##         AXIS["(E)",east,
##             ORDER[1],
##             LENGTHUNIT["metre",1,
##                 ID["EPSG",9001]]],
##         AXIS["(N)",north,
##             ORDER[2],
##             LENGTHUNIT["metre",1,
##                 ID["EPSG",9001]]]]
st_crs(sac.metro.tracts.w)
## Coordinate Reference System:
##   User input: NAD83 
##   wkt:
## GEOGCRS["NAD83",
##     DATUM["North American Datum 1983",
##         ELLIPSOID["GRS 1980",6378137,298.257222101,
##             LENGTHUNIT["metre",1]]],
##     PRIMEM["Greenwich",0,
##         ANGLEUNIT["degree",0.0174532925199433]],
##     CS[ellipsoidal,2],
##         AXIS["latitude",north,
##             ORDER[1],
##             ANGLEUNIT["degree",0.0174532925199433]],
##         AXIS["longitude",east,
##             ORDER[2],
##             ANGLEUNIT["degree",0.0174532925199433]],
##     ID["EPSG",4269]]
#create new dataset with transformed CRS
ca_pts_crs.transformed <- st_transform(ca_pts_crs,st_crs(sac.metro.tracts.w))

st_crs(ca_pts_crs.transformed )
## Coordinate Reference System:
##   User input: NAD83 
##   wkt:
## GEOGCRS["NAD83",
##     DATUM["North American Datum 1983",
##         ELLIPSOID["GRS 1980",6378137,298.257222101,
##             LENGTHUNIT["metre",1]]],
##     PRIMEM["Greenwich",0,
##         ANGLEUNIT["degree",0.0174532925199433]],
##     CS[ellipsoidal,2],
##         AXIS["latitude",north,
##             ORDER[1],
##             ANGLEUNIT["degree",0.0174532925199433]],
##         AXIS["longitude",east,
##             ORDER[2],
##             ANGLEUNIT["degree",0.0174532925199433]],
##     ID["EPSG",4269]]


OK, let’s try this again!

ca_pts_crs.w <- st_join(ca_pts_crs.transformed, sac.metro.tracts.w, join = st_within, left=FALSE)

It worked! OK, now let’s try our map again.

ggplot() +
  geom_sf(data = sac.metro.tracts.w) +
  geom_sf(data = ca_pts_crs.w, fill = "black") +
  labs(title = "Study Participants",
       caption = "Source: Ovarian Cancer Cases") +  
  theme_bw()


Alright, who’s ready for a challenge? Let’s put it all together in one nice map.

ggplot() + 
  geom_sf(data = sac.metro.tracts.w, aes(fill = medincE), color = NA) +
    scale_fill_gradient(low= "white", high = "red", na.value ="gray", name = "Median Income") +  
  geom_sf(data = ca_pts_crs.w, fill = "black") +
  labs(title = "Study Participants Overlaid with Median Income of Sacramento Tracts",
       caption = "Source: American Community Survey and Ovarian Cancer Cases") +
  theme_bw()


Can I just say, you’re very impressive. Well done!


tmap

Whether you prefer tmap or ggplot is up to you, but I find that tmap has some benefits, so let’s focus on its mapping functions next.

tmap uses the same layered logic as ggplot. As we saw last week, the initial command is tm_shape(), which specifies the geography to which the mapping is applied. You then build on tm_shape() by adding one or more elements such as tm_polygons() for polygons, tm_borders() for lines, and tm_dots() for points. All additional functions take on the form of tm_. Check the full list of tm_ elements here.

Choropleth maps in tmap

Let’s make a static choropleth map of median household income in Sacramento MSA just like we did above, but this time in tmap.

tm_shape(sac.metro.tracts.w) +
  tm_polygons(col = "medincE", style = "quantile")
## 
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_polygons()`: instead of `style = "quantile"`, use fill.scale =
## `tm_scale_intervals()`.
## ℹ Migrate the argument(s) 'style' to 'tm_scale_intervals(<HERE>)'


We first put the dataset sac.metro.tracts.w inside tm_shape(). Because you are plotting polygons, you use tm_polygons() next. The argument col = "medincE" tells R to shade (or color) the tracts by the variable medincE. The argument style = "quantile" tells R to break up the shading into quantiles, or equal groups of 5 as a default. I find that this is where tmap offers a distinct advantage over ggplot in that users have greater control over the legend and bin breaks. tmap allows users to specify algorithms to automatically create breaks with the style argument. You can also change the number of breaks by setting n=. The default is n=5. Rather than quintiles, you can show quartiles using n=4. I’m feeling crazy. Let’s do it.

tm_shape(sac.metro.tracts.w) +
  tm_polygons(col = "medincE", style = "quantile",  n=4)
## 
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_polygons()`: instead of `style = "quantile"`, use fill.scale =
## `tm_scale_intervals()`.
## ℹ Migrate the argument(s) 'style', 'n' to 'tm_scale_intervals(<HERE>)'


Check out this link for more on available classification styles in tmap.


The tm_polygons() command is a wrapper around two other functions, tm_fill() and tm_borders(). tm_fill() controls the contents of the polygons (color, classification, etc.), while tm_borders() does the same for the polygon outlines.

For example, using the same shape (but no variable), we obtain the outlines of the neighborhoods from the tm_borders() command.

tm_shape(sac.metro.tracts.w) +
  tm_borders()


Similarly, we obtain a choropleth map without the polygon outlines when we just use the tm_fill() command.

tm_shape(sac.metro.tracts.w) + 
  tm_fill("medincE")

When we combine the two commands, we obtain the same map as with tm_polygons() (this illustrates how in R one can often obtain the same result in a number of different ways). Try this on your own.


Color scheme

The argument palette = defines the color ranges associated with the bins and determined by the style arguments. Several built-in palettes are contained in tmap. For example, using palette = "Reds" would yield the following map for our example.

tm_shape(sac.metro.tracts.w) +
  tm_polygons(col = "medincE", style = "quantile",palette = "Reds") 
## 
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_polygons()`: instead of `style = "quantile"`, use fill.scale =
## `tm_scale_intervals()`.
## ℹ Migrate the argument(s) 'style', 'palette' (rename to 'values') to
##   'tm_scale_intervals(<HERE>)'
## [cols4all] color palettes: use palettes from the R package cols4all. Run
## `cols4all::c4a_gui()` to explore them. The old palette name "Reds" is named
## "brewer.reds"
## Multiple palettes called "reds" found: "brewer.reds", "matplotlib.reds". The first one, "brewer.reds", is returned.

Under the hood, “Reds” refers to one of the color schemes supported by the RColorBrewer package (see below).


In addition to the built-in palettes, customized color ranges can be created by specifying a vector with the desired colors as anchors. This will create a spectrum of colors in the map that range between the colors specified in the vector. For instance, if we used c(“red”, “blue”), the color spectrum would move from red to purple, then to blue, with in between shades. In our example:

tm_shape(sac.metro.tracts.w) +
  tm_polygons(col = "medincE", style = "quantile",palette = c("red","blue")) 
## 
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_polygons()`: instead of `style = "quantile"`, use fill.scale =
## `tm_scale_intervals()`.
## ℹ Migrate the argument(s) 'style', 'palette' (rename to 'values') to
##   'tm_scale_intervals(<HERE>)'


Not exactly a pretty picture. In order to capture a diverging scale, we insert “white” in between red and blue.

tm_shape(sac.metro.tracts.w) +
  tm_polygons(col = "medincE", style = "quantile",palette = c("red","white", "blue")) 
## 
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_polygons()`: instead of `style = "quantile"`, use fill.scale =
## `tm_scale_intervals()`.
## ℹ Migrate the argument(s) 'style', 'palette' (rename to 'values') to
##   'tm_scale_intervals(<HERE>)'


A preferred approach to select a color palette is to chose one of the schemes contained in the RColorBrewer package. These are based on the research of cartographer Cynthia Brewer (see the colorbrewer2 website for details). ColorBrewer makes a distinction between sequential scales (for a scale that goes from low to high), diverging scales (to highlight how values differ from a central tendency), and qualitative scales (for categorical variables). For each scale, a series of single hue and multi-hue scales are suggested. In the RColorBrewer package, these are referred to by a name (e.g., the “Reds” palette we used above is an example). The full list is contained in the RColorBrewer documentation.

There are two very useful commands in this package. One sets a color palette by specifying its name and the number of desired categories. The result is a character vector with the hex codes of the corresponding colors.

For example, we select a sequential color scheme going from blue to green, as BuGn, by means of the command brewer.pal, with the number of categories (6) and the scheme as arguments. The resulting vector contains the HEX codes for the colors.

brewer.pal(6,"BuGn")
## [1] "#EDF8FB" "#CCECE6" "#99D8C9" "#66C2A4" "#2CA25F" "#006D2C"


Using this palette in our map yields the following result.

tm_shape(sac.metro.tracts.w) +
  tm_polygons(col = "medincE", style = "quantile",palette="BuGn") 
## 
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_polygons()`: instead of `style = "quantile"`, use fill.scale =
## `tm_scale_intervals()`.
## ℹ Migrate the argument(s) 'style', 'palette' (rename to 'values') to
##   'tm_scale_intervals(<HERE>)'
## [cols4all] color palettes: use palettes from the R package cols4all. Run
## `cols4all::c4a_gui()` to explore them. The old palette name "BuGn" is named
## "brewer.bu_gn"
## Multiple palettes called "bu_gn" found: "brewer.bu_gn", "matplotlib.bu_gn". The first one, "brewer.bu_gn", is returned.


The command display.brewer.pal() allows us to explore different color schemes before applying them to a map. For example:

display.brewer.pal(6,"BuGn")


Legend

There are many options to change the formatting of the legend. The automatic title for the legend is not that attractive, since it is simply the variable name. This can be customized by setting the title argument.

tm_shape(sac.metro.tracts.w) +
  tm_polygons(col = "medincE", style = "quantile",palette = "Reds",
              title = "Median Income") 
## 
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_polygons()`: instead of `style = "quantile"`, use fill.scale =
## `tm_scale_intervals()`.
## ℹ Migrate the argument(s) 'style', 'palette' (rename to 'values') to
##   'tm_scale_intervals(<HERE>)'
## [v3->v4] `tm_polygons()`: migrate the argument(s) related to the legend of the
## visual variable `fill` namely 'title' to 'fill.legend = tm_legend(<HERE>)'
## [cols4all] color palettes: use palettes from the R package cols4all. Run
## `cols4all::c4a_gui()` to explore them. The old palette name "Reds" is named
## "brewer.reds"
## Multiple palettes called "reds" found: "brewer.reds", "matplotlib.reds". The first one, "brewer.reds", is returned.


Another important aspect of the legend is its positioning. This is handled through the tm_layout() function. This function has a vast number of options, as detailed in the documentation. There are also specialized subsets of layout functions, focused on specific aspects of the map, such as tm_legend(), tm_style() and tm_format(). We illustrate the positioning of the legend.

Often, the default location of the legend is appropriate, but sometimes further control is needed. The legend.position argument to the tm_layout function moves the legend around the map, and it takes a vector of two string variables that determine both the horizontal position (“left”, “right”, or “center”) and the vertical position (“top”, “bottom”, or “center”).

For example, if we would want to move the legend to the bottom-right position, we would use the following set of commands.

tm_shape(sac.metro.tracts.w) +
  tm_polygons(col = "medincE", style = "quantile",palette = "Reds",
              title = "Median Income") +
  tm_layout(legend.position = c("right", "bottom"))
## 
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_polygons()`: instead of `style = "quantile"`, use fill.scale =
## `tm_scale_intervals()`.
## ℹ Migrate the argument(s) 'style', 'palette' (rename to 'values') to
##   'tm_scale_intervals(<HERE>)'
## [v3->v4] `tm_polygons()`: migrate the argument(s) related to the legend of the
## visual variable `fill` namely 'title' to 'fill.legend = tm_legend(<HERE>)'
## [cols4all] color palettes: use palettes from the R package cols4all. Run
## `cols4all::c4a_gui()` to explore them. The old palette name "Reds" is named
## "brewer.reds"
## Multiple palettes called "reds" found: "brewer.reds", "matplotlib.reds". The first one, "brewer.reds", is returned.


There is also the option to position the legend outside the frame of the map. This is accomplished by setting legend.outside to TRUE, and optionally also specify its position by means of legend.outside.position(). The latter can take the values “top”, “bottom”, “right”, and “left”.

For example, to position the legend outside and on the right, would be accomplished by the following commands.

tm_shape(sac.metro.tracts.w) +
  tm_polygons(col = "medincE", style = "quantile",palette = "Reds",
              title = "Median Income") +
  tm_layout(legend.outside = TRUE, legend.outside.position = "right")
## 
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_polygons()`: instead of `style = "quantile"`, use fill.scale =
## `tm_scale_intervals()`.
## ℹ Migrate the argument(s) 'style', 'palette' (rename to 'values') to
##   'tm_scale_intervals(<HERE>)'
## [v3->v4] `tm_polygons()`: migrate the argument(s) related to the legend of the
## visual variable `fill` namely 'title' to 'fill.legend = tm_legend(<HERE>)'
## [cols4all] color palettes: use palettes from the R package cols4all. Run
## `cols4all::c4a_gui()` to explore them. The old palette name "Reds" is named
## "brewer.reds"
## Multiple palettes called "reds" found: "brewer.reds", "matplotlib.reds". The first one, "brewer.reds", is returned.


We can also customize the size of the legend, its alignment, font, etc. Check out the documentation for more!


Title

Another functionality of the tm_layout() function is to set a title for the map, and specify its position, size, etc. For example, we can set the title, the title.size and the title.position as in the example below. We made the font size a bit smaller (0.8) in order not to overwhelm the map, and positioned it in the top left-hand corner.

tm_shape(sac.metro.tracts.w) +
  tm_polygons(col = "medincE", style = "quantile",palette = "Reds",
              title = "Median Income") +
  tm_layout(title = "Median Income of Sacramento Tracts", title.size = 0.8, 
            title.position = c("left","top"),
            legend.outside = TRUE, legend.outside.position = "right")
## 
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_polygons()`: instead of `style = "quantile"`, use fill.scale =
## `tm_scale_intervals()`.
## ℹ Migrate the argument(s) 'style', 'palette' (rename to 'values') to
##   'tm_scale_intervals(<HERE>)'
## [v3->v4] `tm_polygons()`: migrate the argument(s) related to the legend of the
## visual variable `fill` namely 'title' to 'fill.legend = tm_legend(<HERE>)'
## [v3->v4] `tm_layout()`: use `tm_title()` instead of `tm_layout(title = )`
## [cols4all] color palettes: use palettes from the R package cols4all. Run
## `cols4all::c4a_gui()` to explore them. The old palette name "Reds" is named
## "brewer.reds"
## Multiple palettes called "reds" found: "brewer.reds", "matplotlib.reds". The first one, "brewer.reds", is returned.


To have a title appear on top (or on the bottom) of the map, we need to set the main.title argument of the tm_layout() function, with the associated main.title.position, as illustrated below (with title.size set to 1.25 to have a larger font).

tm_shape(sac.metro.tracts.w) +
  tm_polygons(col = "medincE", style = "quantile",palette = "Reds",
              title = "Median Income") +
  tm_layout(main.title = "Median Income of Sacramento Tracts", 
            main.title.size = 1.25, main.title.position="center",
            legend.outside = TRUE, legend.outside.position = "right")
## 
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_polygons()`: instead of `style = "quantile"`, use fill.scale =
## `tm_scale_intervals()`.
## ℹ Migrate the argument(s) 'style', 'palette' (rename to 'values') to
##   'tm_scale_intervals(<HERE>)'
## [v3->v4] `tm_polygons()`: migrate the argument(s) related to the legend of the
## visual variable `fill` namely 'title' to 'fill.legend = tm_legend(<HERE>)'
## [v3->v4] `tm_layout()`: use `tm_title()` instead of `tm_layout(main.title = )`
## [cols4all] color palettes: use palettes from the R package cols4all. Run
## `cols4all::c4a_gui()` to explore them. The old palette name "Reds" is named
## "brewer.reds"
## Multiple palettes called "reds" found: "brewer.reds", "matplotlib.reds". The first one, "brewer.reds", is returned.


Scale bar and arrow

OK this really wouldn’t be a GIS class without talking about one of the core elements of a map–the good ole scale bar and arrow. Let’s add these to our map. First, we add the scale bar with tm_scale_bar().

The argument breaks tells R the distances to break up and end the bar. The argument position places the scale bar on the bottom left part of the map. Note that the scale is in miles (we’re in Amurica!). The default is in kilometers (the rest of the world!), but you can specify the units within tm_shape() using the argument unit. text.size scales the size of the bar smaller (below 1) or larger (above 1).

tm_shape(sac.metro.tracts.w, unit = "mi") +
  tm_polygons(col = "medincE", style = "quantile",palette = "Reds",
              title = "Median Income") +
  tm_scale_bar(breaks = c(0, 5, 10, 20), text.size = 0.75, position = c("left", "bottom")) +
  tm_layout(main.title = "Median Income of Sacramento Tracts", 
            main.title.size = 1.25, main.title.position="center",
            legend.outside = TRUE, legend.outside.position = "right")
## 
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_polygons()`: instead of `style = "quantile"`, use fill.scale =
## `tm_scale_intervals()`.
## ℹ Migrate the argument(s) 'style', 'palette' (rename to 'values') to
##   'tm_scale_intervals(<HERE>)'
## [v3->v4] `tm_polygons()`: migrate the argument(s) related to the legend of the
## visual variable `fill` namely 'title' to 'fill.legend = tm_legend(<HERE>)'
## ! `tm_scale_bar()` is deprecated. Please use `tm_scalebar()` instead.
## [v3->v4] `tm_layout()`: use `tm_title()` instead of `tm_layout(main.title = )`
## [cols4all] color palettes: use palettes from the R package cols4all. Run
## `cols4all::c4a_gui()` to explore them. The old palette name "Reds" is named
## "brewer.reds"
## Multiple palettes called "reds" found: "brewer.reds", "matplotlib.reds". The first one, "brewer.reds", is returned.


Next let’s spice things up by adding a north arrow, which we can do using the function tm_compass(). You can control for the type, size and location of the arrow within this function. I place a 4-star arrow on the bottom right of the map.

tm_shape(sac.metro.tracts.w, unit = "mi") +
  tm_polygons(col = "medincE", style = "quantile",palette = "Reds",
              title = "Median Income") +
  tm_scale_bar(breaks = c(0, 5, 10, 20), text.size = 0.75, 
               position = c("left", "bottom")) +
  tm_compass(type = "4star", position = c("right", "bottom")) +
  tm_layout(main.title = "Median Income of Sacramento Tracts", 
            main.title.size = 1.25, main.title.position="center",
            legend.outside = TRUE, legend.outside.position = "right")
## 
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_polygons()`: instead of `style = "quantile"`, use fill.scale =
## `tm_scale_intervals()`.
## ℹ Migrate the argument(s) 'style', 'palette' (rename to 'values') to
##   'tm_scale_intervals(<HERE>)'
## [v3->v4] `tm_polygons()`: migrate the argument(s) related to the legend of the
## visual variable `fill` namely 'title' to 'fill.legend = tm_legend(<HERE>)'
## ! `tm_scale_bar()` is deprecated. Please use `tm_scalebar()` instead.
## [v3->v4] `tm_layout()`: use `tm_title()` instead of `tm_layout(main.title = )`
## [cols4all] color palettes: use palettes from the R package cols4all. Run
## `cols4all::c4a_gui()` to explore them. The old palette name "Reds" is named
## "brewer.reds"
## Multiple palettes called "reds" found: "brewer.reds", "matplotlib.reds". The first one, "brewer.reds", is returned.


We can also eliminate the frame around the map using the argument frame = FALSE.

sac.map <- tm_shape(sac.metro.tracts.w, unit = "mi") +
  tm_polygons(col = "medincE", style = "quantile",palette = "Reds",
              title = "Median Income") +
  tm_scale_bar(breaks = c(0, 5, 10, 20), text.size = 0.75, position = c("left", "bottom")) +
  tm_compass(type = "4star", position = c("right", "bottom")) +
  tm_layout(main.title = "Median Income of Sacramento tracts", 
            main.title.size = 1.25, frame = FALSE,
            main.title.position="center",
            legend.outside = TRUE, legend.outside.position = "right")
## 
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_polygons()`: instead of `style = "quantile"`, use fill.scale =
## `tm_scale_intervals()`.
## ℹ Migrate the argument(s) 'style', 'palette' (rename to 'values') to
##   'tm_scale_intervals(<HERE>)'
## [v3->v4] `tm_polygons()`: migrate the argument(s) related to the legend of the
## visual variable `fill` namely 'title' to 'fill.legend = tm_legend(<HERE>)'
## ! `tm_scale_bar()` is deprecated. Please use `tm_scalebar()` instead.
## [v3->v4] `tm_layout()`: use `tm_title()` instead of `tm_layout(main.title = )`
sac.map
## [cols4all] color palettes: use palettes from the R package cols4all. Run
## `cols4all::c4a_gui()` to explore them. The old palette name "Reds" is named
## "brewer.reds"
## Multiple palettes called "reds" found: "brewer.reds", "matplotlib.reds". The first one, "brewer.reds", is returned.


Note that I saved the map into an object called sac.map. R is an object-oriented program, so everything you make in R are objects that can be saved for future manipulation. This includes maps. And future manipulations of a saved map includes adding more tm_* functions to the saved object, such as sac.map + tm_layout(your changes here). Check the help documentation for tm_layout() to see the complete list of settings.


Saving maps

You can save your maps a few ways. 1. On the plotting screen where the map is shown, click on Export and save it as either an image or pdf file. 2. Use the function tmap_save()

For option 2, we can save the map object sac.map as such:

tmap_save(sac.map, "sac_city_inc.jpg")
## [cols4all] color palettes: use palettes from the R package cols4all. Run
## `cols4all::c4a_gui()` to explore them. The old palette name "Reds" is named
## "brewer.reds"
## Multiple palettes called "reds" found: "brewer.reds", "matplotlib.reds". The first one, "brewer.reds", is returned.
## 
## [cols4all] color palettes: use palettes from the R package cols4all. Run
## `cols4all::c4a_gui()` to explore them. The old palette name "Reds" is named
## "brewer.reds"
## Multiple palettes called "reds" found: "brewer.reds", "matplotlib.reds". The first one, "brewer.reds", is returned.
## 
## Map saved to /Users/pjames1/Dropbox/UC Davis Folders/SPH 215 GIS and Public Health/Github_Website/SPH215/sac_city_inc.jpg
## 
## Resolution: 2598.137 by 1697.37 pixels
## 
## Size: 8.660457 by 5.6579 inches (300 dpi)

Specify the tmap object and a filename with an extension. It supports .pdf, .eps, .svg, .wmf, .png, .jpg, .bmp and .tiff. The default is .png. Also make sure you’ve set your working directory to the folder that you want your map to be saved in.


Making a map with CDC Places data

OK, do we have energy for one more example? Let’s bring in data from the CDC Places dataset. This is an incredible resource to access data on the CDC’s Behavioral Risk Factor and Surveillance System (BRFSS), as well as social determinants of health data from American Community Survey. I’ve already downloaded California Census tract data on the “% of adults reporting no leisure-time physical activity”. Let’s bring this in and take a look at it!

places_ca_lpa<-read_csv("/Users/pjames1/Dropbox/UC Davis Folders/SPH 215 GIS and Public Health/Github_Website/SPH215/places_ca_lpa.csv")
## Rows: 9070 Columns: 24
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (16): StateAbbr, StateDesc, CountyName, CountyFIPS, LocationName, DataSo...
## dbl  (6): Year, Data_Value, Low_Confidence_Limit, High_Confidence_Limit, Tot...
## lgl  (2): Data_Value_Footnote_Symbol, Data_Value_Footnote
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
glimpse(places_ca_lpa)
## Rows: 9,070
## Columns: 24
## $ Year                       <dbl> 2022, 2022, 2022, 2022, 2022, 2022, 2022, 2…
## $ StateAbbr                  <chr> "CA", "CA", "CA", "CA", "CA", "CA", "CA", "…
## $ StateDesc                  <chr> "California", "California", "California", "…
## $ CountyName                 <chr> "Orange", "Riverside", "Riverside", "Sacram…
## $ CountyFIPS                 <chr> "06059", "06065", "06065", "06067", "06073"…
## $ LocationName               <chr> "06059063010", "06065042509", "06065042519"…
## $ DataSource                 <chr> "BRFSS", "BRFSS", "BRFSS", "BRFSS", "BRFSS"…
## $ Category                   <chr> "Health Risk Behaviors", "Health Risk Behav…
## $ Measure                    <chr> "No leisure-time physical activity among ad…
## $ Data_Value_Unit            <chr> "%", "%", "%", "%", "%", "%", "%", "%", "%"…
## $ Data_Value_Type            <chr> "Crude prevalence", "Crude prevalence", "Cr…
## $ Data_Value                 <dbl> 13.1, 33.6, 40.2, 26.6, 32.9, 12.9, 18.7, 2…
## $ Data_Value_Footnote_Symbol <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ Data_Value_Footnote        <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ Low_Confidence_Limit       <dbl> 11.3, 30.0, 36.3, 23.4, 29.6, 11.2, 16.4, 1…
## $ High_Confidence_Limit      <dbl> 15.0, 37.4, 44.2, 29.9, 36.3, 14.7, 21.1, 2…
## $ TotalPopulation            <dbl> 6698, 3460, 1887, 7420, 3601, 4831, 4778, 3…
## $ TotalPop18plus             <dbl> 5354, 2491, 1297, 5680, 2534, 4017, 3684, 2…
## $ Geolocation                <chr> "POINT (-117.8983938 33.6281163)", "POINT (…
## $ LocationID                 <chr> "06059063010", "06065042509", "06065042519"…
## $ CategoryID                 <chr> "RISKBEH", "RISKBEH", "RISKBEH", "RISKBEH",…
## $ MeasureId                  <chr> "LPA", "LPA", "LPA", "LPA", "LPA", "LPA", "…
## $ DataValueTypeID            <chr> "CrdPrv", "CrdPrv", "CrdPrv", "CrdPrv", "Cr…
## $ Short_Question_Text        <chr> "Physical Inactivity", "Physical Inactivity…


Interesting stuff. Looks like the values we care about are stores in a column called Data_Value and the FIPS code seems to be in LocationName. Let’s go ahead and rename LocationName and then see if we can join this data with our Census tract data ca.tracts.

places_ca_lpa<-rename(places_ca_lpa, GEOID = LocationName)
glimpse(places_ca_lpa)
## Rows: 9,070
## Columns: 24
## $ Year                       <dbl> 2022, 2022, 2022, 2022, 2022, 2022, 2022, 2…
## $ StateAbbr                  <chr> "CA", "CA", "CA", "CA", "CA", "CA", "CA", "…
## $ StateDesc                  <chr> "California", "California", "California", "…
## $ CountyName                 <chr> "Orange", "Riverside", "Riverside", "Sacram…
## $ CountyFIPS                 <chr> "06059", "06065", "06065", "06067", "06073"…
## $ GEOID                      <chr> "06059063010", "06065042509", "06065042519"…
## $ DataSource                 <chr> "BRFSS", "BRFSS", "BRFSS", "BRFSS", "BRFSS"…
## $ Category                   <chr> "Health Risk Behaviors", "Health Risk Behav…
## $ Measure                    <chr> "No leisure-time physical activity among ad…
## $ Data_Value_Unit            <chr> "%", "%", "%", "%", "%", "%", "%", "%", "%"…
## $ Data_Value_Type            <chr> "Crude prevalence", "Crude prevalence", "Cr…
## $ Data_Value                 <dbl> 13.1, 33.6, 40.2, 26.6, 32.9, 12.9, 18.7, 2…
## $ Data_Value_Footnote_Symbol <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ Data_Value_Footnote        <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ Low_Confidence_Limit       <dbl> 11.3, 30.0, 36.3, 23.4, 29.6, 11.2, 16.4, 1…
## $ High_Confidence_Limit      <dbl> 15.0, 37.4, 44.2, 29.9, 36.3, 14.7, 21.1, 2…
## $ TotalPopulation            <dbl> 6698, 3460, 1887, 7420, 3601, 4831, 4778, 3…
## $ TotalPop18plus             <dbl> 5354, 2491, 1297, 5680, 2534, 4017, 3684, 2…
## $ Geolocation                <chr> "POINT (-117.8983938 33.6281163)", "POINT (…
## $ LocationID                 <chr> "06059063010", "06065042509", "06065042519"…
## $ CategoryID                 <chr> "RISKBEH", "RISKBEH", "RISKBEH", "RISKBEH",…
## $ MeasureId                  <chr> "LPA", "LPA", "LPA", "LPA", "LPA", "LPA", "…
## $ DataValueTypeID            <chr> "CrdPrv", "CrdPrv", "CrdPrv", "CrdPrv", "Cr…
## $ Short_Question_Text        <chr> "Physical Inactivity", "Physical Inactivity…
ca.tracts.lpa <- ca.tracts %>%
  left_join(places_ca_lpa, by = "GEOID")
glimpse(ca.tracts.lpa)
## Rows: 9,129
## Columns: 41
## $ GEOID                      <chr> "06001442700", "06001442800", "06037204920"…
## $ Tract                      <chr> "Census Tract 4427", "Census Tract 4428", "…
## $ County                     <chr> "Alameda County", "Alameda County", "Los An…
## $ State                      <chr> "California", "California", "California", "…
## $ tpoprE                     <dbl> 2998, 3087, 2459, 3690, 3402, 3409, 3490, 8…
## $ tpoprM                     <dbl> 475, 386, 387, 446, 438, 624, 605, 350, 509…
## $ nhwhiteE                   <dbl> 921, 779, 68, 14, 236, 15, 500, 1379, 138, …
## $ nhwhiteM                   <dbl> 168, 183, 33, 14, 131, 24, 264, 165, 49, 83…
## $ nhblkE                     <dbl> 119, 12, 49, 8, 65, 8, 17, 2633, 18, 18, 93…
## $ nhblkM                     <dbl> 115, 49, 51, 12, 93, 10, 75, 344, 22, 19, 9…
## $ nhasnE                     <dbl> 1577, 1671, 0, 0, 123, 70, 1030, 238, 151, …
## $ nhasnM                     <dbl> 469, 356, 14, 14, 164, 104, 235, 76, 82, 56…
## $ hispE                      <dbl> 308, 561, 2331, 3642, 2978, 3303, 1919, 354…
## $ hispM                      <dbl> 81, 181, 387, 446, 481, 628, 519, 285, 519,…
## $ NAME                       <chr> "Census Tract 4427; Alameda County; Califor…
## $ medincE                    <dbl> 199154, 180800, 70500, 52262, 110967, 28516…
## $ medincM                    <dbl> 30525, 28293, 15698, 5659, 22149, 3246, 285…
## $ Year                       <dbl> 2022, 2022, 2022, 2022, 2022, 2022, 2022, 2…
## $ StateAbbr                  <chr> "CA", "CA", "CA", "CA", "CA", "CA", "CA", "…
## $ StateDesc                  <chr> "California", "California", "California", "…
## $ CountyName                 <chr> "Alameda", "Alameda", "Los Angeles", "Los A…
## $ CountyFIPS                 <chr> "06001", "06001", "06037", "06037", "06037"…
## $ DataSource                 <chr> "BRFSS", "BRFSS", "BRFSS", "BRFSS", "BRFSS"…
## $ Category                   <chr> "Health Risk Behaviors", "Health Risk Behav…
## $ Measure                    <chr> "No leisure-time physical activity among ad…
## $ Data_Value_Unit            <chr> "%", "%", "%", "%", "%", "%", "%", "%", "%"…
## $ Data_Value_Type            <chr> "Crude prevalence", "Crude prevalence", "Cr…
## $ Data_Value                 <dbl> 14.2, 18.8, 38.2, 38.7, 25.3, 40.2, 30.7, 2…
## $ Data_Value_Footnote_Symbol <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ Data_Value_Footnote        <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ Low_Confidence_Limit       <dbl> 12.2, 16.3, 35.2, 35.7, 22.9, 37.3, 28.1, 2…
## $ High_Confidence_Limit      <dbl> 16.6, 21.6, 41.1, 41.7, 27.5, 43.1, 33.2, 2…
## $ TotalPopulation            <dbl> 3141, 2959, 2537, 3850, 3632, 3858, 3335, 5…
## $ TotalPop18plus             <dbl> 2432, 2359, 1903, 2599, 2822, 2783, 2695, 5…
## $ Geolocation                <chr> "POINT (-122.0081094 37.5371514)", "POINT (…
## $ LocationID                 <chr> "06001442700", "06001442800", "06037204920"…
## $ CategoryID                 <chr> "RISKBEH", "RISKBEH", "RISKBEH", "RISKBEH",…
## $ MeasureId                  <chr> "LPA", "LPA", "LPA", "LPA", "LPA", "LPA", "…
## $ DataValueTypeID            <chr> "CrdPrv", "CrdPrv", "CrdPrv", "CrdPrv", "Cr…
## $ Short_Question_Text        <chr> "Physical Inactivity", "Physical Inactivity…
## $ geometry                   <MULTIPOLYGON [°]> MULTIPOLYGON (((-122.0172 3...…


It worked! OK, now let’s make a map of % of adults reporting no leisure-time physical activity. We will build on what we’ve learned above! I’ve also included the option in tm_polygons() of lwd = 0 which makes the borders a width of 0…basically we are making it so there are no borders and we can easily see polygon values in denser concentrations of Census tracts (e.g., around Sacramento, San Francisco, LA, etc.)

tm_shape(ca.tracts.lpa, unit = "mi") +
  tm_polygons(col = "Data_Value", style = "quantile",palette = "Reds",
              title = "% Adults No Physical Activity", lwd = 0) +
  tm_layout(main.title = "% of Adults Reporting No Leisure Time Physical Activity", 
            main.title.size = 1.25, main.title.position="center")
## 
## ── tmap v3 code detected ───────────────────────────────────────────────────────
## [v3->v4] `tm_polygons()`: instead of `style = "quantile"`, use fill.scale =
## `tm_scale_intervals()`.
## ℹ Migrate the argument(s) 'style', 'palette' (rename to 'values') to
##   'tm_scale_intervals(<HERE>)'
## [v3->v4] `tm_polygons()`: migrate the argument(s) related to the legend of the
## visual variable `fill` namely 'title' to 'fill.legend = tm_legend(<HERE>)'
## [v3->v4] `tm_layout()`: use `tm_title()` instead of `tm_layout(main.title = )`
## [cols4all] color palettes: use palettes from the R package cols4all. Run
## `cols4all::c4a_gui()` to explore them. The old palette name "Reds" is named
## "brewer.reds"
## Multiple palettes called "reds" found: "brewer.reds", "matplotlib.reds". The first one, "brewer.reds", is returned.
## 
## [plot mode] fit legend/component: Some legend items or map compoments do not
## fit well, and are therefore rescaled.
## ℹ Set the tmap option `component.autoscale = FALSE` to disable rescaling.


Oooooooo that is one goooood looking map!


You’ve completed your introduction to sf. Whew! Badge? Yes, please, you earned it! Time to celebrate!


sf Badge
sf Badge


LS0tCnRpdGxlOiAiTGFiIDM6IEdlb2NvZGluZywgVmVjdG9yIERhdGEsIGFuZCBDZW5zdXMgRGF0YSIKLS0tCgpJbiB0aGlzIGxhYiwgd2UgYXJlIGdvaW5nIHRvIHdvcmsgd2l0aCB2ZWN0b3IgZGF0YSwgd2hpY2ggd2UndmUgdGFsa2VkIGFib3V0IGxhc3Qgd2Vlay4gRmlyc3QsIHdlIGFyZSBnb2luZyB0byBnZW9jb2RlIGFkZHJlc3Nlcy4gIFdlIGFyZSBnb2luZyB0byB3b3JrIHdpdGggYSBkYXRhc2V0IG9mIGNhbmNlciBwYXRpZW50cyBhY3Jvc3MgQ2FsaWZvcm5pYSwgYXMgd2VsbCBhcyBDZW5zdXMgZGF0YSBvbiBzb2Npb2Vjb25vbWljIGZhY3RvcnMuIFdlIHdpbGwgYWxzbyB0YWxrIGFib3V0IGhvdyB0byB2aXN1YWxpemUgZGF0YS4gCgpUaGUgb2JqZWN0aXZlcyBvZiB0aGlzIGd1aWRlIGFyZSB0byB0ZWFjaCB5b3U6CgoxLiBIb3cgdG8gZ2VvY29kZSBhZGRyZXNzZXMKMi4gSG93IHRvIGJyaW5nIGluIGFuZCB2aXN1YWxpemUgcG9pbnQgZGF0YQozLiBIb3cgdG8gZG93bmxvYWQgQ2Vuc3VzIGRhdGEgdXNpbmcgdGhlIENlbnN1cyBBUEkKNC4gSG93IHRvIGNvbmR1Y3QgZXhwbG9yYXRvcnkgZGF0YSBhbmFseXNpcwoKTGV0J3MgZ2V0IGNyYWNraW5nIQoKXAoKIyBPcGVuIHVwIGFuIFIgTWFya2Rvd24gZmlsZQoKV2UgaG9wZWZ1bGx5IHJlbWVtYmVyIHNvbWUgb2YgdGhpcyBmcm9tIGxhc3Qgd2VlayBpbiBbTGFiIDJdKExhYjJfMjAyNi5odG1sKSwgYnV0IGxldCdzIG9wZW4gYW4gUiBNYXJrZG93biBmaWxlIGJ5IGNsaWNraW5nIG9uICpGaWxlKiBhdCB0aGUgdG9wIG1lbnUgaW4gUlN0dWRpbywgc2VsZWN0ICpOZXcgRmlsZSosIGFuZCB0aGVuICpSIE1hcmtkb3duLi4uKi4gQSB3aW5kb3cgc2hvdWxkIHBvcCB1cC4gSW4gdGhhdCB3aW5kb3csIGZvciAqdGl0bGUqLCBwdXQgaW4g4oCcTGFiIDPigJ0uIEZvciAqYXV0aG9yKiwgcHV0IHlvdXIgbmFtZS4gTGVhdmUgdGhlIEhUTUwgcmFkaW8gYnV0dG9uIGNsaWNrZWQsIGFuZCBzZWxlY3QgT0suIEEgbmV3IFIgTWFya2Rvd24gZmlsZSBzaG91bGQgcG9wIHVwIGluIHRoZSB0b3AgbGVmdCB3aW5kb3cuCgpcCgojIFdoYXQgcGFja2FnZXMgZG8gd2UgbmVlZD8KCkxldCdzIGxvYWQgc29tZSBwYWNrYWdlcyB0aGF0IHdlIHdpbGwgbmVlZCB0aGlzIHdlZWsuIFdlIG5lZWQgdG8gbG9hZCBhbnkgcGFja2FnZXMgd2UgcHJldmlvdXNseSBpbnN0YWxsZWQgIHVzaW5nIHRoZSBmdW5jdGlvbiBgbGlicmFyeSgpYC4gUmVtZW1iZXIsIGluc3RhbGwgb25jZSwgbG9hZCBldmVyeSB0aW1lLiBBbmQgaWYgaXQgZ2l2ZXMgeW91IGFuIGVycm9yIGZvciBgbm8gcGFja2FnZSBjYWxsZWQuLi5gLCB0aGVuIHdlIG5lZWQgdG8gaW5zdGFsbCB0aG9zZSBwYWNrYWdlcyB1c2luZyBgaW5zdGFsbC5wYWNrYWdlcygpYC4gU28gd2hlbiB1c2luZyBhIHBhY2thZ2UsIGBsaWJyYXJ5KClgIHNob3VsZCBhbHdheXMgYmUgYXQgdGhlIHRvcCBvZiB5b3VyIFIgTWFya2Rvd24uCgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkodGlkeWdlb2NvZGVyKQpsaWJyYXJ5KHNmKQpsaWJyYXJ5KE1hcEdBTSkKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkodGlkeWNlbnN1cykKbGlicmFyeShmbGV4dGFibGUpCmxpYnJhcnkodG1hcCkKYGBgCgpcCgojIEdlb2NvZGluZwoKRmlyc3QsIHdlIGFyZSBnb2luZyB0byB0YWNrbGUgaG93IHdlIHRha2UgYWRkcmVzc2VzIGFuZCBjb252ZXJ0IHRoZW0gdG8gc3BhdGlhbCBkYXRhIChsYXRpdHVkZSBhbmQgbG9uZ2l0dWRlKS4gU28sIGxldCdzIHNheSB3ZSB3YW50ZWQgdG8gbWFwIGFsbCBvZiB0aGUgbWFyaWp1YW5hIGRpc3BlbnNhcmllcyBhY3Jvc3MgU2FuIEZyYW5jaXNjby4gTGV0J3MgZG93bmxvYWQgYSAuY3N2IG9mIHRoZXNlIGFkZHJlc3NlcyBmcm9tIHRoZSBHaXRodWIgc2l0ZSwgdGhlbiB0YWtlIGEgbG9vayBhdCB0aGUgZGF0YXNldC4KCmBgYHtyfQpkb3dubG9hZC5maWxlKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vcGphbWVzLXVjZGF2aXMvU1BIMjE1L3JlZnMvaGVhZHMvbWFpbi9zYW5fZnJhbmNpc2NvX2FjdGl2ZV9tYXJpanVhbmFfcmV0YWlsZXJzLmNzdiIsICJzYW5fZnJhbmNpc2NvX2FjdGl2ZV9tYXJpanVhbmFfcmV0YWlsZXJzLmNzdiIsIG1vZGUgPSAid2IiKQoKc2ZfbWogPC0gcmVhZF9jc3YoInNhbl9mcmFuY2lzY29fYWN0aXZlX21hcmlqdWFuYV9yZXRhaWxlcnMuY3N2IikKCmhlYWQoc2ZfbWopICAgICAgICAgICAgICAgICAgCgpgYGAKClwKCk9LLCBzb21lIGludGVyZXN0aW5nIGNvbHVtbnMgdGhlcmUsIGFuZCB3ZSBoYXZlICpQcmVtaXNlIEFkZHJlc3MqIGFzIGEgY29sdW1uIHRoYXQgd2UgbWlnaHQgd2FudCB0byBtYWtlIHNwYXRpYWwuIExldCdzIGxvb2sgY2xvc2VyIGF0IHRoYXQuCgoKYGBge3J9CmhlYWQoc2ZfbWokYFByZW1pc2UgQWRkcmVzc2ApCmBgYAoKXAoKT0sgdGhhdCBjb2x1bW4gbG9va3MgbGlrZSB3aGF0IHdlIHdhbnQgdG8gZ2VvY29kZS4gQnV0IGhvdyBkbyB3ZSB0YWtlIHRoZXNlIGFkZHJlc3NlcyBhbmQgbWFrZSB0aGVtIGludG8gc3BhdGlhbCBpbmZvcm1hdGlvbj8gV2UgaGF2ZSB0byBnZW9jb2RlIHRoZW0hIFRvIGRvIHNvLCB3ZSB3aWxsIHVzZSB0aGUgKip0aWR5Z2VvY29kZXIqKiBwYWNrYWdlIGluIFIuIEJ1dCBmaXJzdCwgd2Ugc2VlIHRoYXQgdGhlIGFkZHJlc3NlcyBsb29rIGEgbGl0dGxlIHN0cmFuZ2UuIFRoZSBhZGRyZXNzIGNvdW50eSBpcyBhbHdheXMgIkNvdW50eTogU0FOIEZSQU5DSVNDTyIgc28gd2Ugd2lsbCBgZ3N1YigpYCBvdXQgdGhhdCBlbnRpcmUgc3RyaW5nLgpgYGB7cn0Kc2ZfbWokYFByZW1pc2UgQWRkcmVzc2AgPC0gZ3N1YigiIENvdW50eTogU0FOIEZSQU5DSVNDTyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiIiwgc2ZfbWokYFByZW1pc2UgQWRkcmVzc2ApCmhlYWQoc2ZfbWokYFByZW1pc2UgQWRkcmVzc2ApCmBgYApUaGF0IGxvb2tzIG11Y2ggYmV0dGVyLgoKXAoKTm93IGxldCdzIGdpdmUgYSB0cnkgdG8gZ2VvY29kaW5nIHRoZXNlIGFkZHJlc3NlcyB3aXRoIHRoZSAqKnRpZHlnZW9jb2RlcioqIHBhY2thZ2UuIFdlIHdpbGwgdXNlIHRoZSBgZ2VvY29kZSgpYCBmdW5jdGlvbiB0byBhZGQgYSBsYXRpdHVkZSBhbmQgbG9uZ2l0dWRlIHRvIGVhY2ggb2Ygb3VyIGFkZHJlc3NlcyBpbiB0aGUgKlByZW1pc2UgQWRkcmVzcyogY29sdW1uLiBXZSB3aWxsIHVzZSB0aGUgT3BlbiBTdHJlZXQgTWFwIGFkZHJlc3MgZGF0YWJhc2UgYnkgc3BlY2lmeWluZyBgbWV0aG9kID0gIm9zbSJgLiBUaGlzIHdpbGwgdGFrZSBhYm91dCBhIG1pbnV0ZSB0byBydW4sIHNvIGJlIHBhdGllbnQhIApgYGB7cn0Kc2ZfbWpfZ2VvICAgICAgPC0gZ2VvY29kZShzZl9taiwgIlByZW1pc2UgQWRkcmVzcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gIm9zbSIpCmhlYWQoc2ZfbWpfZ2VvKQpgYGAKClwKCkhtbSwgbG9va3MgbGlrZSBzb21lIG9mIG91ciBhZGRyZXNzZXMgaGF2ZSBhbiBgTkFgIGZvciB0aGVpciBsYXQgYW5kIGxvbmcuIExldCdzIHRha2UgYSBjbG9zZXIgbG9vay4KCmBgYHtyfQpzdW1tYXJ5KHNmX21qX2dlbyRsYXQpCnN1bW1hcnkoc2ZfbWpfZ2VvJGxvbmcpCmBgYAoKXAoKTG9va3MgbGlrZSB3ZSBoYXZlIDEwIGFkZHJlc3NlcyBtaXNzaW5nICpsYXQqIGFuZCAxMCBtaXNzaW5nICpsb25nKi4gTGV0J3MgdHJ5IHRoaXMgYWdhaW4gdXNpbmcgYSBkaWZmZXJlbnQgZ2VvY29kaW5nIGRhdGFiYXNlIGNhbGxlZCAqYXJjZ2lzKi4KCmBgYHtyfQpzZl9tal9nZW9fYXJjICAgICAgPC0gZ2VvY29kZShzZl9taiwgIlByZW1pc2UgQWRkcmVzcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gImFyY2dpcyIpCmhlYWQoc2ZfbWpfZ2VvX2FyYykKc3VtbWFyeShzZl9tal9nZW9fYXJjJGxhdCkKc3VtbWFyeShzZl9tal9nZW9fYXJjJGxvbmcpCmBgYAoKXAoKV29vaG9vISBObyBtaXNzaW5nbmVzcy4gTG92ZSB0byBzZWUgaXQuIE9LLCBsZXQncyBwbG90IHRoZXNlIGRhdGEgYW5kIHNlZSBob3cgdGhleSBsb29rLiAKCmBgYHtyfQpwbG90KHNmX21qX2dlb19hcmMkbG9uZywgc2ZfbWpfZ2VvX2FyYyRsYXQpCmBgYAoKXAoKV2UgYXJlIGluIGJ1c2luZXNzISBXZSBoYXZlIHRha2VuIGFkZHJlc3NlcyBhbmQgY29udmVydGVkIHRoZW0gaW50byBsYXRpdHVkZSBhbmQgbG9uZ2l0dWRlISBJIHRoaW5rIHdlIG5lZWQgYSBiYWRnZSEKIVt0aWR5Z2VvY29kZXIgQmFkZ2VdKHRpZHlnZW9jb2Rlcl9oZXgucG5nKSAKClwKCkJvbnVzIGV4ZXJjaXNlISBMZXQncyB0YWtlIHRoZXNlIGFkZHJlc3NlcyBhbmQgKnJldmVyc2UgZ2VvY29kZSB0aGVtKi4gVGhhdCdzIGp1c3QgYSBmYW5jeSB3YXkgb2Ygc2F5aW5nIHRoYXQgd2Ugd2lsbCB0YWtlIGxhdGl0dWRlIGFuZCBsb25naXR1ZGUgZGF0YSBhbmQgY29udmVydCBpdCBpbnRvIHJlYWRhYmxlIGFkZHJlc3Nlcy4gV2UgdXNlIHRoZSBhcHRseSBuYW1lZCBmdW5jdGlvbiBgcmV2ZXJzZV9nZW9jb2RlYCBhbmQgc3BlY2lmeSB3aGljaCBjb2x1bW5zIHRvIGxvb2sgYXQgKCpsYXQqIGFuZCAqbG9uZyopLCB0aGUgbWV0aG9kIHdlIHdhbnQgZm9yIGdlb2NvZGluZywgIGFuZCB3aGF0IHdlIHdhbnQgdGhlICphZGRyZXNzKiBjb2x1bW4gdG8gYmUgbmFtZWQuIFRoZW4gd2UgYHNlbGVjdGAgb3V0IHNvbWUgY29sdW1ucyB0aGF0IHdlIGFyZW4ndCByZWFsbHkgaW50ZXJlc3RlZCBpbi4gUmVtZW1iZXIsIHdlIGFyZSBkb2luZyB0aGlzIHRoZSAqKnRpZHkqKiB3YXkgc28gd2UgYXJlIHVzaW5nIGAlPiVgIHBpcGVzLgoKYGBge3J9CnJldmVyc2UgPC0gc2ZfbWpfZ2VvX2FyYyAlPiUKICByZXZlcnNlX2dlb2NvZGUobGF0ID0gbGF0LCBsb25nID0gbG9uZywgbWV0aG9kID0gJ2FyY2dpcycsCiAgICAgICAgICAgICAgICAgIGFkZHJlc3MgPSBhZGRyZXNzX2ZvdW5kKSAlPiUKICBzZWxlY3QoLWBCdXNpbmVzcyBPd25lcmAsLWBCdXNpbmVzcyBTdHJ1Y3R1cmVgLC1gTGljZW5zZSBOdW1iZXJgLC1gTGljZW5zZSBUeXBlYCwtU3RhdHVzLC1gSXNzdWUgRGF0ZWAsLWBFeHBpcmF0aW9uIERhdGVgLC1BY3Rpdml0aWVzLC1gQWR1bHQtVXNlL01lZGljaW5hbGApCmhlYWQocmV2ZXJzZSkKYGBgCgpMb29raW5nIGF0ICpQcmVtaXNlIEFkZHJlc3MqIGFuZCAqYWRkcmVzc19mb3VuZCogd2UgY2FuIHNlZSB0aGF0IHdlIGRpZCBwcmV0dHkgd2VsbCEgTm90IHBlcmZlY3QsIGJ1dCBtb3N0IGFyZSB0aGUgcmlnaHQgYWRkcmVzcyBvciBhIGZldyBkb29ycyBkb3duLiBXZWxsIGRvbmUhCgpcCgojIHNmOiBPdXIgZ28gdG8gcGFja2FnZSBmb3IgdmVjdG9yIGRhdGEKCkFsdGhvdWdoIHRoZXJlIGFyZSBhIGZldyB3YXlzIHRvIHdvcmsgd2l0aCB2ZWN0b3Igc3BhdGlhbCBkYXRhIGluIFIsIHdlIHdpbGwgZm9jdXMgb24gdGhlICoqc2YqKiBwYWNrYWdlIGluIHRoaXMgY291cnNlLiBUaGUgbWFqb3JpdHkgb2Ygc3BhdGlhbCBmb2xrcyBpbiBSIGhhdmUgc2hpZnRlZCB0byAqKnNmKiogZm9yIHZlY3RvciBkYXRhLCBhbmQgc28gaXQgbWFrZXMgc2Vuc2UgdG8gZm9jdXMgb24gaXQgaW4gdGhlIGNsYXNzLgoKUHJvY2Vzc2luZyBzcGF0aWFsIGRhdGEgaXMgdmVyeSBzaW1pbGFyIHRvIG5vbnNwYXRpYWwgZGF0YSB0aGFua3MgdG8gdGhlIHBhY2thZ2UgKipzZioqLCB3aGljaCBpcyB0aWR5IGZyaWVuZGx5LiAqKnNmKiogc3RhbmRzIGZvciBzaW1wbGUgZmVhdHVyZXMuIFRoZSBbU2ltcGxlIEZlYXR1cmVzIHN0YW5kYXJkXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9TaW1wbGVfRmVhdHVyZXMpIGRlZmluZXMgYSBzaW1wbGUgZmVhdHVyZSBhcyBhIHJlcHJlc2VudGF0aW9uIG9mIGEgcmVhbCB3b3JsZCBvYmplY3QgYnkgYSBwb2ludCBvciBwb2ludHMgdGhhdCBtYXkgb3IgbWF5IG5vdCBiZSBjb25uZWN0ZWQgYnkgc3RyYWlnaHQgbGluZSBzZWdtZW50cyB0byBmb3JtIGxpbmVzIG9yIHBvbHlnb25zLiBBIGZlYXR1cmUgaXMgdGhvdWdodCBvZiBhcyBhIHRoaW5nLCBvciBhbiBvYmplY3QgaW4gdGhlIHJlYWwgd29ybGQsIHN1Y2ggYXMgYSBidWlsZGluZyBvciBhIHRyZWUuIEEgY291bnR5IGNhbiBiZSBhIGZlYXR1cmUuIEFzIGNhbiBhIGNpdHkgYW5kIGEgbmVpZ2hib3Job29kLiBGZWF0dXJlcyBoYXZlIGEgZ2VvbWV0cnkgZGVzY3JpYmluZyB3aGVyZSBvbiBFYXJ0aCB0aGUgZmVhdHVyZXMgYXJlIGxvY2F0ZWQsIGFuZCB0aGV5IGhhdmUgYXR0cmlidXRlcywgd2hpY2ggZGVzY3JpYmUgb3RoZXIgcHJvcGVydGllcy4gCgpOb3cgbGV0J3MgZ2V0IG91ciBoYW5kcyBkaXJ0eSB3b3JraW5nIHdpdGggc29tZSBzcGF0aWFsIGRhdGEuCgpcCgojIFZlY3RvcnM6IEltcG9ydCBDYW5jZXIgUG9pbnQgRGF0YQoKRm9yIHRoaXMgbGFiLCB3ZSB3aWxsIHByaW1hcmlseSBiZSB3b3JraW5nIHdpdGggdGhlIFsqTWFwR0FNKiBwYWNrYWdlXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvTWFwR0FNL2luZGV4Lmh0bWwpLiBJZiB5b3UgZ28gdG8gdGhlIGxpbmssIHlvdSBjYW4gcmVhZCB0aGUgcmVmZXJlbmNlIG1hbnVhbCBvbiB0aGUgdmFyaW91cyBkYXRhc2V0cyBhdmFpbGFibGUgaW4gdGhlIHBhY2thZ2UuIEZvciB0aGlzIGxhYiwgd2Ugd2lsbCBtYWlubHkgYmUgd29ya2luZyB3aXRoIHRoZSAqQ0FkYXRhKiBkYXRhc2V0LiBXaGlsZSB0aGV5IGFyZSBiYXNlZCBvbiByZWFsIHBhdHRlcm5zIGV4cGVjdGVkIGluIG9ic2VydmF0aW9uYWwgZXBpZGVtaW9sb2dpYyBzdHVkaWVzLCB0aGVzZSBkYXRhIGhhdmUgYmVlbiBzaW11bGF0ZWQgYW5kIGFyZSBmb3IgdGVhY2hpbmcgcHVycG9zZXMgb25seS4gVGhlIGRhdGEgY29udGFpbiA1MDAwIHNpbXVsYXRlZCBvdmFyaWFuIGNhbmNlciBjYXNlcy4gVGhpcyBpcyBhIGNvaG9ydCB3aXRoICoqdGltZSB0byBtb3J0YWxpdHkqKiBtZWFzdXJlZCwgYnV0IGZvciB0aGUgcHVycG9zZXMgb2Ygb3VyIGNsYXNzLCB3ZSB3aWxsIGNvbmR1Y3Qgc2ltcGxlIHRhYnVsYXIgYW5hbHlzZXMgbG9va2luZyBhdCBhc3NvY2lhdGlvbnMgYmV0d2VlbiBzcGF0aWFsIGV4cG9zdXJlcyB3aXRoIG1vcnRhbGl0eSBhdCBlbmQgb2YgZm9sbG93LXVwLgoKVGhlICpDQWRhdGEqIGRhdGFzZXQgY29udGFpbnMgdGhlIGZvbGxvd2luZyB2YXJpYWJsZXMKCi0gKnRpbWUqIChmb2xsb3ctdXAgdGltZSB0byBlaXRoZXIgZXZlbnQgb2YgYmVpbmcgY2Vuc29yZWQpCi0gKmV2ZW50KiAoMT1kZWFkLCAwPWNlbnNvcmVkKQotICpYKiAoTGF0aXR1ZGUpCi0gKlkqIChMb25naXR1ZGUpCi0gKkFHRSogKGFnZSBpbiB5ZWFycykKLSAqSU5TKiAoaW5zdXJhbmNlIHN0YXR1cywgY2F0ZWdvcmljYWwpCgpcCgpTbyBsZXQncyBicmluZyBpbiB0aGUgKkNBZGF0YSogZGF0YXNldCBhbmQgaGF2ZSBhIGxvb2sgYXQgaXQuIAoKYGBge3J9CiNMb2FkIENBZGF0YSBkYXRhc2V0IGZyb20gTWFwR0FNIHBhY2thZ2UKZGF0YShDQWRhdGEpCmNhX3B0cyA8LSBDQWRhdGEKc3VtbWFyeShjYV9wdHMpCmBgYAoKXAoKT0ssIHNvIHRoZSB2YXJpYWJsZXMgbG9vayBncmVhdC4gSXMgaXQgYSBzcGF0aWFsIGRhdGFzZXQgdGhhdCBjYW4gYmUgcmVjb2duaXplZCBieSBSPyBOb3QganVzdCB5ZXQuIFdlIGtub3cgdGhhdCAqWCogaXMgbGlrZWx5IHNvbWUgc29ydCBvZiBsb25naXR1ZGUgY29sdW1uIGFuZCAqWSogaXMgbGlrZWx5IHNvbWUgc29ydCBvZiBsYXRpdHVkZSBjb2x1bW4sIGFsdGhvdWdoIHRoZXkgZG9uJ3QgZXhhY3RseSBsb29rIHJpZ2h0LiBXZSBoYXZlIHRvIHRlbGwgUiB0aGF0IHRoZSBYIGFuZCBZIGNvb3JkaW5hdGVzIGFyZSBzcGF0aWFsIGRhdGEgdXNpbmcgdGhlIGBzdF9hc19zZmAgZnVuY3Rpb24gaW4gKipzZioqLiBXaXRoIHRoaXMgY29tbWFuZCwgd2UgY2FuIHNwZWNpZnkgd2hpY2ggY29vcmRpbmF0ZXMgUiBzaG91bGQgbG9vayBhdCBmb3IgbG9uZ2l0dWRlIGFuZCBsYXRpdHVkZSB3aXRoIHRoZSBgY29vcmRzPWMoKWAgZnVuY3Rpb24uCgpcCgpgYGB7cn0KY2FfcHRzIDwtIHN0X2FzX3NmKENBZGF0YSwgY29vcmRzPWMoIlgiLCJZIikpCmBgYAoKXAoKTGV0J3MgY2hlY2sgdGhlIGNvb3JkaW5hdGUgcmVmZXJlbmNlIHN5c3RlbSAoQ1JTKSB1c2luZyB0aGUgYHN0X2Nyc2AgY29tbWFuZCBpbiB0aGUgKipzZioqIHBhY2thZ2UuCgpgYGB7cn0Kc3RfY3JzKGNhX3B0cykKYGBgCgpIbW1tLCBOQS4gVGhhdCBzdGlsbCBkb2Vzbid0IGxvb2sgZ29vZC4gU28gaG93IGRvIHdlIG1ha2UgdGhpcyBhIHNwYXRpYWwgZmlsZT8gV2Ugd2lsbCBuZWVkIHRvIGFkZCBhIENSUy4gCgpcCgojIyBBZGQgY29vcmRpbmF0ZSByZWZlcmVuY2Ugc3lzdGVtCgpMZXQncyBhZGQgYSBDUlMgYnkgdXNpbmcgYHN0X3NldF9zZmAgZnJvbSB0aGUgKipzZioqIHBhY2thZ2UuIFdlIGdldCB0aGUgQ1JTIGZvciB0aGlzIGRhdGFzZXQgZnJvbSB0aGUgTWFwR0FNIGRvY3VtZW50YXRpb24gKGRvbid0IHdvcnJ5LS1pdCB0b29rIG1lIGZvcmV2ZXIgdG8gZmluZCB0aGlzLCBidXQgdXN1YWxseSB0aGlzIGlzIG11Y2ggZWFzaWVyIHRvIGZpbmQpLiBUaGVuIHdlIHdpbGwgZG91YmxlIGNoZWNrIHRoZSBDUlMuCgpgYGB7cn0KI0xvYWQgdGhlIHByb2plY3Rpb24gaW50byBhbiBvYmplY3QgY2FsbGVkIGNhX3Byb2oKCmNhX3Byb2ogPC0gIitwcm9qPWxjYyArbGF0XzE9NDAgK2xhdF8yPTQxLjY2NjY2NjY2NjY2NjY2IAogICAgICAgICAgICAgK2xhdF8wPTM5LjMzMzMzMzMzMzMzMzM0ICtsb25fMD0tMTIyICt4XzA9MjAwMDAwMCAKICAgICAgICAgICAgICt5XzA9NTAwMDAwLjAwMDAwMDAwMDIgK2VsbHBzPUdSUzgwIAogICAgICAgICAgICAgK2RhdHVtPU5BRDgzICt1bml0cz1tICtub19kZWZzIgoKI1NldCBDUlMKY2FfcHRzX2NycyA8LSBzdF9zZXRfY3JzKGNhX3B0cywgY2FfcHJvaikKCiNMb29rIGF0IGRhdGFzZXQKc3VtbWFyeShjYV9wdHNfY3JzKSAKCiNDaGVjayB0aGUgQ1JTCnN0X2NycyhjYV9wdHNfY3JzKQpgYGAKClwKCiMgdG1hcDogTWFwcGluZyB2ZWN0b3IgZGF0YQoKTmljZSEgV2UgaGF2ZSBhIHNwYXRpYWwgZGF0YXNldC4gVGhhdCAqZ2VvbWV0cnkqIGNvbHVtbiBpcyBob3cgKipzZioqIHN0b3JlcyB0aGUgZ2VvZ3JhcGhpYyBkYXRhLCBhbmQgbGF0aXR1ZGUgYW5kIGxvbmdpdHVkZSB0aGVyZSBhcmUgbG9va2luZyBhIGJpdCBtb3JlIGxpa2Ugd2Ugd291bGQgZXhwZWN0LiBBbmQgd2UgZGVmaW5pdGVseSBoYXZlIGEgZnVsbCBDUlMgd2l0aCBhbGwgc29ydHMgb2YgaW5mby4gT0ssIGxldCdzIHBsb3Qgb3VyIGRhdGEgdG8gbWFrZSBzdXJlIHRoZXkgbG9vayBzcGF0aWFsISBXZSB3aWxsIHVzZSB0aGUgKip0bWFwKiogcGFja2FnZSB0byBwbG90IHRoZSBwb2ludHMuIFdlIHdpbGwgZmlyc3Qgc3BlY2lmeSB0aGUgYHRtYXBfbW9kZWAgb2YgInZpZXciIHdoaWNoIGlzIGludGVyYWN0aXZlLiBUaGVyZSdzIGFsc28gYSAicGxvdCIgb3B0aW9uIHdoaWNoIGlzIG5pY2UgZm9yIG1ha2luZyBleHBvcnRhYmxlIGZpZ3VyZXMuIFdlIHdpbGwgdGhlbiBjcmVhdGUgYW4gb2JqZWN0IGNhbGxlZCAqY2FuY2VyX21hcCogYW5kIHRoZW4gYWRkIGEgbGF5ZXIgd2l0aCBgdG1fc2hhcGUoKWAuIFRoaXMgYWxsb3dzIHVzIHRvIGNvbWJpbmUgc2V2ZXJhbCBtYXBzIGludG8gb25lLCBvciB0byBhZGQgbGF5ZXJzIG9uIHRvcCBvZiBlYWNoIG90aGVyLiBUaGVuIHdlIGhhdmUgdG8gc3BlY2lmeSBhIGxldmVsIG9mIHRoYXQgbGF5ZXIgdG8gZGlzcGxheS4gSGVyZSB3ZSB3aWxsIHVzZSBgdG1fZG90cygpYCB0byB0byBwbG90IHRoZSBwb2ludHMuIEluIG91ciBvcHRpb25zLCB3ZSBzcGVjaWZ5IHRoZSBzaXplIHdpdGggYHNpemU9YC4gCmBgYHtyfQp0bWFwX21vZGUoInZpZXciKQpjYW5jZXJfbWFwID0gdG1fc2hhcGUoY2FfcHRzX2NycykgKyB0bV9kb3RzKHNpemU9MC41KQpjYW5jZXJfbWFwCmBgYAoKXAoKTGV0J3MgcGxheSBhcm91bmQgd2l0aCBzb21lIG9mIHRoZSBvcHRpb25zLiBXZSBjYW4gY2hhbmdlIHRoZSBjb2xvciB3aXRoIHRoZSBgY29sPWAgb3B0aW9uLiBXZSBjYW4gbWFrZSB0aGUgZG90cyBzbWFsbGVyIGJ5IHNwZWNpZnlpbmcgdGhlIGBzaXplPWAgb3B0aW9uLiBBbmQgd2UgY2FuIGNoYW5nZSB0aGUgdHJhbnNwYXJlbmN5IG9mIHRoZSBwb2ludHMgd2l0aCB0aGUgYGFscGhhPWAgb3B0aW9uLgpgYGB7cn0KY2FuY2VyX21hcF9zbWFsbCA9IHRtX3NoYXBlKGNhX3B0c19jcnMpICsgdG1fZG90cyhjb2wgPSAiYmx1ZSIsIHNpemUgPSAwLjMsIGFscGhhID0gMC41KQpjYW5jZXJfbWFwX3NtYWxsCmBgYAoKXAoKTGV0J3MgbWFwIHRoZSBwb2ludHMgY29sb3IgY29kZWQgYnkgdGhlIHZhcmlhYmxlICpldmVudCosIG9yIHdoZXRoZXIgb3Igbm90IHRoZSBwYXJ0aWNpcGFudCBkaWVkIG92ZXIgZm9sbG93dXAuIFdlIGRvIHRoYXQgYnkgc3BlY2lmeWluZyB0aGF0IHRoZSBjb2xvciAoYGNvbD1gKSBpcyBiYXNlZCBvbiB0aGUgY29sdW1uICpldmVudCouIFdlIGhhdmUgdG8gc3BlY2lmeSB0aGF0ICpldmVudCogaXMgYSBjYXRlZ29yaWNhbCB2YXJpYWJsZSB3aXRoIGBzdHlsZT0iY2F0ImAuIApgYGB7cn0KY2FuY2VyX21hcF9ldmVudHMgPSB0bV9zaGFwZShjYV9wdHNfY3JzKSArIHRtX2RvdHMoc2l6ZT0wLjMsIGNvbD0iZXZlbnQiLCBzdHlsZT0iY2F0IikKY2FuY2VyX21hcF9ldmVudHMKYGBgCgpcCgpMZXQncyBzZWUgaWYgd2UgY2FuIGNoYW5nZSB1cCB0aGUgY29sb3Igc2NoZW1lLgpgYGB7cn0KY2FuY2VyX21hcF9ldmVudHNfcmcgPSB0bV9zaGFwZShjYV9wdHNfY3JzKSArIHRtX2RvdHMoY29sID0gImV2ZW50IiwgcGFsZXR0ZSA9IGMoIjAiID0gImdyYXkiLCAiMSIgPSAicmVkIiksIHN0eWxlPSJjYXQiKQpjYW5jZXJfbWFwX2V2ZW50c19yZwpgYGAKClwKCkxvb2tpbmcgZ29vZCEgWW91IGVhcm5lZCB5b3Vyc2VsZiBhIHRtYXAgYmFkZ2UuIE5vdyBnZXQgeW91cnNlbGYgYSBbY29va2llXShodHRwczovL3lvdXR1LmJlLzNzb0M2VkFEcGN3P3NpPTRuakJpYjNqV1BzVVlhRVMpLgoKXAoKIVt0bWFwIEJhZGdlXSh0bWFwbG9nby5wbmcpCgpcCgojIERvd25sb2FkaW5nIENlbnN1cyBEYXRhCgpPbmUgb2YgdGhlIHByaW1hcnkgc291cmNlcyBvZiBkYXRhIHRoYXQgd2XigJlsbCBiZSB1c2luZyBpbiB0aGlzIGNsYXNzIGlzIHRoZSBVbml0ZWQgU3RhdGVzIERlY2VubmlhbCBDZW5zdXMgYW5kIHRoZSBBbWVyaWNhbiBDb21tdW5pdHkgU3VydmV5LiBUaGVyZSBhcmUgdHdvIHdheXMgdG8gYnJpbmcgQ2Vuc3VzIGRhdGEgaW50byBSOiBVc2luZyBhbmQgQVBJIG9yIGRvd25sb2FkaW5nIGl0IGZyb20gYW4gb25saW5lIHNvdXJjZS4KCioqTm90ZSB0aGF0IHdlIHdpbGwgZ2F0aGVyIEFDUyBkYXRhIGZyb20gYWxsIHNvdXJjZXMuIENlbnN1cyBib3VuZGFyaWVzIGNoYW5nZWQgaW4gMjAyMCwgd2hpY2ggbWVhbnMgdGhhdCAyMDE2LTIwMjAgYW5kIGxhdGVyIGRhdGEgd2lsbCBub3QgY29tcGxldGVseSBtZXJnZSB3aXRoIEFDUyBkYXRhIGJlZm9yZSAyMDIwLiBTbyBtYWtlIHN1cmUgeW91IG1lcmdlIDIwMjAgZGF0YSBvbmx5IHdpdGggMjAyMCBkYXRhIChidXQgeW91IGNhbiBtZXJnZSAyMDE5IGRhdGEgd2l0aCBkYXRhIGJldHdlZW4gMjAxMC0yMDE5KS4gVGhpcyBpcyBlc3BlY2lhbGx5IGltcG9ydGFudCBmb3IgdHJhY3QgZGF0YSwgd2l0aCBtYW55IG5ldyB0cmFjdHMgY3JlYXRlZCBpbiAyMDIwIGFuZCBleGlzdGluZyB0cmFjdHMgZXhwZXJpZW5jaW5nIGRyYW1hdGljIGNoYW5nZXMgaW4gdGhlaXIgYm91bmRhcmllcyBiZXR3ZWVuIDIwMTAgYW5kIDIwMjAuIFNlZSB0aGUgaW1wYWN0IG9mIHRyYWN0IGJvdW5kYXJ5IGNoYW5nZXMgYmV0d2VlbiAyMDAwIGFuZCAyMDEwIFtoZXJlXShodHRwczovL2NyZDIzMC5naXRodWIuaW8vY2Vuc3VzZ2VvZ3JhcGh5Lmh0bWwpLioqIFlvdSBtYXkgYWxzbyBleHBsb3JlIHRoZSBbTmVpZ2hib3Job29kIENoYW5nZSBEYXRhYmFzZV0oaHR0cHM6Ly9zZWFyY2gubGlicmFyeS51Y2RhdmlzLmVkdS92aWV3L2FjdGlvbi91cmVzb2x2ZXIuZG8/b3BlcmF0aW9uPXJlc29sdmVTZXJ2aWNlJnBhY2thZ2Vfc2VydmljZV9pZD0yODEwNDIyMzg2MDAwMzEyNiZpbnN0aXR1dGlvbklkPTMxMjYmY3VzdG9tZXJJZD0zMTI1JlZFPXRydWUpIHdoaWNoIGlzIGF2YWlsYWJsZSB0aHJvdWdoIHRoZSBVQyBEYXZpcyBsaWJyYXJ5LCBhbmQgaXMgYSBkYXRhc2V0IHRoYXQgaW5jb3Jwb3JhdGVzIHRyYWN0IGJvdW5kYXJ5IGNoYW5nZXMgb3ZlciB0aW1lLiBXZSBhcmUgd29ya2luZyBvbiBhY3F1aXJpbmcgdGhlIDIwMjAgZGF0YSB0aGVyZSEKClwKCiMjIERvd25sb2FkIENlbnN1cyBkYXRhIGZyb20gYW4gb25saW5lIHNvdXJjZQoKVGhlIGZpcnN0IHdheSB0byBvYnRhaW4gQ2Vuc3VzIGRhdGEgaXMgdG8gZG93bmxvYWQgdGhlbSBkaXJlY3RseSBmcm9tIHRoZSB3ZWIgb250byB5b3VyIGhhcmQgZHJpdmUuIFRoZXJlIGFyZSBzZXZlcmFsIHdlYnNpdGVzIHdoZXJlIHlvdSBjYW4gZG93bmxvYWQgQ2Vuc3VzIGRhdGEgaW5jbHVkaW5nIFtTb2NpYWwgRXhwbG9yZXJdKGh0dHBzOi8vd3d3LnNvY2lhbGV4cGxvcmVyLmNvbS8pIGFuZCBbUG9saWN5TWFwXShodHRwczovL3VjZGF2aXMucG9saWN5bWFwLmNvbS9tYXBzKSwgd2hpY2ggd2UgaGF2ZSBmcmVlIGFjY2VzcyB0byBhcyBVQyBEYXZpcyBhZmZpbGlhdGVzLCBhbmQgdGhlIFtOYXRpb25hbCBIaXN0b3JpY2FsIEdlb2dyYXBoaWMgSW5mb3JtYXRpb24gU3lzdGVtIChOSEdJUyldKGh0dHBzOi8vd3d3Lm5oZ2lzLm9yZy8pLCB3aGljaCBpcyBmcmVlIGZvciBldmVyeW9uZS4gVG8gZmluZCBvdXQgaG93IHRvIGRvd25sb2FkIGRhdGEgZnJvbSBQb2xpY3lNYXAgYW5kIE5IR0lTLCBjaGVjayBvdXQgdHV0b3JpYWxzIFtoZXJlXShodHRwczovL3BvbGljeW1hcC5oZWxwZG9jcy5pby9kYXRhLWRvd25sb2FkKSBhbmQgW2hlcmVdKGh0dHBzOi8vd3d3Lm5oZ2lzLm9yZy91c2VyLXJlc291cmNlcy91c2Vycy1ndWlkZSkuCgpcCgojIyBVc2UgdGhlIENlbnN1cyBBUEkgYW5kIHRpZHljZW5zdXMKClRoZSBvdGhlciB3YXkgdG8gYnJpbmcgQ2Vuc3VzIGRhdGEgaW50byBSIGlzIHRvIHVzZSB0aGUgW0NlbnN1cyBBcHBsaWNhdGlvbiBQcm9ncmFtIEludGVyZmFjZSAoQVBJKV0oaHR0cHM6Ly93d3cuY2Vuc3VzLmdvdi9kYXRhL2RldmVsb3BlcnMvZ3VpZGFuY2UvYXBpLXVzZXItZ3VpZGUuV2hhdF9pc190aGVfQVBJLmh0bWwpLiBBbiBBUEkgYWxsb3dzIGZvciBkaXJlY3QgcmVxdWVzdHMgZm9yIGRhdGEgaW4gbWFjaGluZS1yZWFkYWJsZSBmb3JtLiBUaGF0IGlzLCByYXRoZXIgdGhhbiB5b3UgaGF2aW5nIHRvIG5hdmlnYXRlIHRvIHNvbWUgd2Vic2l0ZSwgc2Nyb2xsIGFyb3VuZCB0byBmaW5kIGEgZGF0YXNldCwgZG93bmxvYWQgdGhhdCBkYXRhc2V0IG9uY2UgeW91IGZpbmQgaXQsIHNhdmUgdGhhdCBkYXRhIG9udG8geW91ciBoYXJkIGRyaXZlLCBhbmQgdGhlbiBicmluZyB0aGUgZGF0YSBpbnRvIFIsIHlvdSBqdXN0IHRlbGwgUiB0byByZXRyaWV2ZSBkYXRhIGRpcmVjdGx5IGZyb20gdGhlIHNvdXJjZSB1c2luZyBvbmUgb3IgdHdvIGxpbmVzIG9mIGNvZGUuCgpJbiBvcmRlciB0byBkaXJlY3RseSBkb3dubG9hZCBkYXRhIGZyb20gdGhlIENlbnN1cyBBUEksIHlvdSBuZWVkIGEga2V5LiBZb3UgY2FuIHNpZ24gdXAgZm9yIGEgZnJlZSBrZXkgW2hlcmVdKGh0dHA6Ly9hcGkuY2Vuc3VzLmdvdi9kYXRhL2tleV9zaWdudXAuaHRtbCksIHdoaWNoIHlvdSBzaG91bGQgaGF2ZSBhbHJlYWR5IGRvbmUgYmVmb3JlIHRoZSBsYWIuIFR5cGUgeW91ciBrZXkgaW4gcXVvdGVzIHVzaW5nIHRoZSBjZW5zdXNfYXBpX2tleSgpIGNvbW1hbmQuCmBgYHtyLCBldmFsID0gRkFMU0V9CmNlbnN1c19hcGlfa2V5KCJZT1VSIEFQSSBLRVkgR09FUyBIRVJFIiwgaW5zdGFsbCA9IFRSVUUpCmBgYAoKYGBge3IsIGVjaG89RkFMU0UsIGV2YWw9RkFMU0V9CmNlbnN1c19hcGlfa2V5KCI1ZDY4OTM1Yzk2YzI2ZWU2N2NhNTJlYjk3M2Q3MWU0YTdiODQ5MGFkIiwgaW5zdGFsbCA9IFRSVUUsIG92ZXJ3cml0ZT1UUlVFKQpgYGAKClwKClRoZSBvcHRpb24gYGluc3RhbGwgPSBUUlVFYCBzYXZlcyB0aGUgQVBJIGtleSBpbiB5b3VyIFIgZW52aXJvbm1lbnQsIHdoaWNoIG1lYW5zIHlvdSBkb27igJl0IGhhdmUgdG8gcnVuIGBjZW5zdXNfYXBpX2tleSgpYCBldmVyeSBzaW5nbGUgdGltZS4gVGhlIGZ1bmN0aW9uIGZvciBkb3dubG9hZGluZyBBbWVyaWNhbiBDb21tdW5pdHkgU3VydmV5IChBQ1MpIENlbnN1cyBkYXRhIGlzIGBnZXRfYWNzKClgLiBUaGUgY29tbWFuZCBmb3IgZG93bmxvYWRpbmcgZGVjZW5uaWFsIENlbnN1cyBkYXRhIGlzIGBnZXRfZGVjZW5uaWFsKClgLiBCb3RoIGZ1bmN0aW9ucyBjb21lIGZyb20gdGhlICoqdGlkeWNlbnN1cyoqIHBhY2thZ2UsIHdoaWNoIGFsbG93cyB1c2VycyB0byBpbnRlcmZhY2Ugd2l0aCB0aGUgVVMgQ2Vuc3VzIEJ1cmVhdeKAmXMgZGVjZW5uaWFsIENlbnN1cyBhbmQgQW1lcmljYW4gQ29tbXVuaXR5IFN1cnZleSBBUElzLiBHZXR0aW5nIHZhcmlhYmxlcyB1c2luZyB0aGUgQ2Vuc3VzIEFQSSByZXF1aXJlcyBrbm93aW5nIHRoZSB2YXJpYWJsZSBJRCAtIGFuZCB0aGVyZSBhcmUgdGhvdXNhbmRzIG9mIHZhcmlhYmxlcyAoYW5kIHRodXMgdGhvdXNhbmRzIG9mIElEcykgYWNyb3NzIHRoZSBkaWZmZXJlbnQgQ2Vuc3VzIGZpbGVzLiBUbyByYXBpZGx5IHNlYXJjaCBmb3IgdmFyaWFibGVzLCB1c2UgdGhlIGNvbW1hbmRzIGBsb2FkX3ZhcmlhYmxlcygpYCBhbmQgYFZpZXcoKWAuIEJlY2F1c2Ugd2XigJlsbCBiZSB1c2luZyB0aGUgQUNTIGluIHRoaXMgZ3VpZGUsIGxldOKAmXMgY2hlY2sgdGhlIHZhcmlhYmxlcyBpbiB0aGUgbW9zdCByZWNlbnQgMjAyMyA1LXllYXIgQUNTICgyMDE5LTIwMjMpIHVzaW5nIHRoZSBmb2xsb3dpbmcgY29tbWFuZHMuCmBgYHtyfQphY3MyMDIzIDwtIGxvYWRfdmFyaWFibGVzKDIwMjMsICJhY3M1IiwgY2FjaGUgPSBUUlVFKQpWaWV3KGFjczIwMjMpCmBgYAoKXAoKQSB3aW5kb3cgc2hvdWxkIGhhdmUgcG9wcGVkIHVwIHNob3dpbmcgeW91IGEgcmVjb3JkIGxheW91dCBvZiB0aGUgMjAxOS0yMDIzIEFDUy4gVG8gc2VhcmNoIGZvciBzcGVjaWZpYyBkYXRhLCBzZWxlY3Qg4oCcRmlsdGVy4oCdIGxvY2F0ZWQgYXQgdGhlIHRvcCBsZWZ0IG9mIHRoaXMgd2luZG93IGFuZCB1c2UgdGhlIHNlYXJjaCBib3hlcyB0aGF0IHBvcCB1cC4gRm9yIGV4YW1wbGUsIHR5cGUgaW4g4oCcSGlzcGFuaWPigJ0gaW4gdGhlIGJveCB1bmRlciDigJxMYWJlbOKAnS4gWW91IHNob3VsZCBzZWUgbmVhciB0aGUgdG9wIG9mIHRoZSBsaXN0IHRoZSBmaXJzdCBzZXQgb2YgdmFyaWFibGVzIHdl4oCZbGwgd2FudCB0byBkb3dubG9hZCAtIHJhY2UvZXRobmljaXR5LiBBbm90aGVyIHdheSBvZiBmaW5kaW5nIHZhcmlhYmxlIG5hbWVzIGlzIHRvIHNlYXJjaCB0aGVtIHVzaW5nIFtTb2NpYWwgRXhwbG9yZXJdKGh0dHBzOi8vd3d3LnNvY2lhbGV4cGxvcmVyLmNvbS9kYXRhL21ldGFkYXRhLykuIENsaWNrIG9uIHRoZSBhcHByb3ByaWF0ZSBzdXJ2ZXkgZGF0YSB5ZWFyIGFuZCB0aGVuIOKAnEFtZXJpY2FuIENvbW11bml0eSBTdXJ2ZXkgVGFibGVz4oCdLCB3aGljaCB3aWxsIHRha2UgeW91IHRvIGEgbGlzdCBvZiB2YXJpYWJsZXMgd2l0aCB0aGVpciBDZW5zdXMgSURzLgoKTGV04oCZcyBleHRyYWN0IHJhY2UvZXRobmljaXR5IGRhdGEgYW5kIHRvdGFsIHBvcHVsYXRpb24gZm9yIFtDYWxpZm9ybmlhIGNvdW50aWVzXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9MaXN0X29mX2NvdW50aWVzX2luX0NhbGlmb3JuaWEpIHVzaW5nIHRoZSBgZ2V0X2FjcygpYCBjb21tYW5kLgoKXAoKYGBge3J9CmNhIDwtIGdldF9hY3MoZ2VvZ3JhcGh5ID0gImNvdW50eSIsIAogICAgICAgICAgICAgIHllYXIgPSAyMDIzLAogICAgICAgICAgICAgIHZhcmlhYmxlcyA9IGModHBvcHIgPSAiQjAzMDAyXzAwMSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgbmh3aGl0ZSA9ICJCMDMwMDJfMDAzIiwgbmhibGsgPSAiQjAzMDAyXzAwNCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgbmhhc24gPSAiQjAzMDAyXzAwNiIsIGhpc3AgPSAiQjAzMDAyXzAxMiIpLCAKICAgICAgICAgICAgICBzdGF0ZSA9ICJDQSIsCiAgICAgICAgICAgICAgc3VydmV5ID0gImFjczUiLAogICAgICAgICAgICAgIG91dHB1dCA9ICJ3aWRlIikKCmBgYAoKXAoKSW4gdGhlIGFib3ZlIGNvZGUsIHdlIHNwZWNpZmllZCB0aGUgZm9sbG93aW5nIGFyZ3VtZW50cwoKLSBgZ2VvZ3JhcGh5YDogVGhlIGxldmVsIG9mIGdlb2dyYXBoeSB3ZSB3YW50IHRoZSBkYXRhIGluOyBpbiBvdXIgY2FzZSwgdGhlIGNvdW50eS4gT3RoZXIgZ2VvZ3JhcGhpYyBvcHRpb25zIGNhbiBiZSBmb3VuZCBbaGVyZV0oaHR0cHM6Ly93YWxrZXJrZS5naXRodWIuaW8vdGlkeWNlbnN1cy9hcnRpY2xlcy9iYXNpYy11c2FnZS5odG1sI2dlb2dyYXBoeS1pbi10aWR5Y2Vuc3VzKS4KLSBgeWVhcmA6IFRoZSBlbmQgeWVhciBvZiB0aGUgZGF0YSAoYmVjYXVzZSB3ZSB3YW50IDIwMTYtMjAyMCwgd2UgdXNlIDIwMjApLgotIGB2YXJpYWJsZXNgOiBUaGUgdmFyaWFibGVzIHdlIHdhbnQgdG8gYnJpbmcgaW4gYXMgc3BlY2lmaWVkIGluIGEgdmVjdG9yIHlvdSBjcmVhdGUgdXNpbmcgdGhlIGZ1bmN0aW9uIGBjKClgLiBOb3RlIHRoYXQgd2UgY3JlYXRlZCB2YXJpYWJsZSBuYW1lcyBvZiBvdXIgb3duIChlLmcuIOKAnG5od2hpdGXigJ0pIGFuZCB3ZSBwdXQgdGhlIEFDUyBJRHMgaW4gcXVvdGVzICjigJxCMDMwMDJfMDAz4oCdKS4gSGFkIHdlIG5vdCBkb25lIHRoaXMsIHRoZSB2YXJpYWJsZSBuYW1lcyB3aWxsIGNvbWUgaW4gYXMgdGhleSBhcmUgbmFtZWQgaW4gdGhlIEFDUywgd2hpY2ggYXJlIG5vdCB2ZXJ5IGRlc2NyaXB0aXZlLgotIGBzdGF0ZWA6IFdlIGNhbiBmaWx0ZXIgdGhlIGNvdW50aWVzIHRvIHRob3NlIGluIGEgc3BlY2lmaWMgc3RhdGUuIEhlcmUgaXQgaXMg4oCcQ0HigJ0gZm9yIENhbGlmb3JuaWEuIElmIHdlIGRvbuKAmXQgc3BlY2lmeSB0aGlzLCB3ZSBnZXQgYWxsIGNvdW50aWVzIGluIHRoZSBVbml0ZWQgU3RhdGVzLgotIGBzdXJ2ZXlgOiBUaGUgc3BlY2lmaWMgQ2Vuc3VzIHN1cnZleSB3ZXJlIGV4dHJhY3RpbmcgZGF0YSBmcm9tLiBXZSB3YW50IGRhdGEgZnJvbSB0aGUgNS15ZWFyIEFtZXJpY2FuIENvbW11bml0eSBTdXJ2ZXksIHNvIHdlIHNwZWNpZnkg4oCcYWNzNeKAnS4gVGhlIEFDUyBjb21lcyBpbiAxLSBhbmQgNS15ZWFyIC0gdmFyaWV0aWVzLgotIGBvdXRwdXRgOiBUaGUgYXJndW1lbnQgdGVsbHMgUiB0byByZXR1cm4gYSB3aWRlIGRhdGFzZXQgYXMgb3Bwb3NlZCB0byBhIGxvbmcgZGF0YXNldCAoc2VlIHRoaXMgdmlnbmV0dGUgZm9yIG1vcmUgaW5mbykuCgpBbm90aGVyIHVzZWZ1bCBvcHRpb24gdG8gc2V0IGlzIGBjYWNoZV90YWJsZSA9IFRSVUVgLCBzbyB5b3UgZG9u4oCZdCBoYXZlIHRvIHJlLWRvd25sb2FkIGFmdGVyIHlvdeKAmXZlIGRvd25sb2FkZWQgc3VjY2Vzc2Z1bGx5IHRoZSBmaXJzdCB0aW1lLiBUeXBlIGluIGA/IGdldF9hY3MoKWAgdG8gc2VlIHRoZSBmdWxsIGxpc3Qgb2Ygb3B0aW9ucy4KClwKCkFzIHlvdSBsZWFybmVkIGluIFtMYWIgMV0oTGFiMV8yMDI2Lmh0bWwpIGFuZCBbTGFiIDJdKExhYjJfMjAyNi5odG1sKSwgd2hlbmV2ZXIgeW91IGJyaW5nIGluIGEgZGF0YXNldCwgdGhlIGZpcnN0IHRoaW5nIHlvdSBzaG91bGQgYWx3YXlzIGRvIGlzIHZpZXcgaXQgdG8gZ2V0IGEgc2Vuc2Ugb2YgaXRzIHN0cnVjdHVyZSBhbmQgdG8gbWFrZSBzdXJlIHlvdSBnb3Qgd2hhdCB5b3UgZXhwZWN0ZWQuIE9uZSB3YXkgb2YgZG9pbmcgdGhpcyBpcyB0byB1c2UgdGhlIGBnbGltcHNlKClgIGNvbW1hbmQKCmBgYHtyfQpnbGltcHNlKGNhKQpgYGAKClwKCllvdSBnZXQgYSBxdWljaywgY29tcGFjdCBzdW1tYXJ5IG9mIHlvdXIgdGliYmxlLiBZb3UgY2FuIGFsc28gdXNlIHRoZSBgaGVhZCgpYCBjb21tYW5kLCB3aGljaCBzaG93cyB5b3UgdGhlIGZpcnN0IHNldmVyYWwgcm93cyBvZiB5b3VyIGRhdGEgb2JqZWN0IChgdGFpbCgpYCB3aWxsIGdpdmUgeW91IHRoZSBsYXN0IHNldmVyYWwgcm93cykuCgpgYGB7cn0KaGVhZChjYSkKYGBgCgpcCgpUaGUgdGliYmxlIGNvbnRhaW5zIGNvdW50aWVzIHdpdGggdGhlaXIgZXN0aW1hdGVzIGZvciByYWNlL2V0aG5pY2l0eS4gVGhlc2UgdmFyaWFibGVzIGVuZCB3aXRoIHRoZSBsZXR0ZXIg4oCcReKAnS4gSXQgYWxzbyBjb250YWlucyB0aGUgW21hcmdpbnMgb2YgZXJyb3JdKGh0dHBzOi8vd2Fsa2VyLWRhdGEuY29tL3RpZHljZW5zdXMvYXJ0aWNsZXMvbWFyZ2lucy1vZi1lcnJvci5odG1sKSBmb3IgZWFjaCBlc3RpbWF0ZS4gVGhlc2UgdmFyaWFibGVzIGVuZCB3aXRoIHRoZSBsZXR0ZXIg4oCcTeKAnS4KCioqdGlkeWNlbnN1cyoqIGlzIGEgZ2FtZSBjaGFuZ2VyIGluIGJlaW5nIGFibGUgdG8gYnJpbmcgaW4gQ2Vuc3VzIGRhdGEgaW50byBSIGluIGEgY29udmVuaWVudCwgZmFzdCwgZWZmaWNpZW50IGFuZCB0aWR5IGZyaWVuZGx5IHdheS4gV2XigJlsbCBiZSB1c2luZyB0aGlzIHBhY2thZ2UgaW4gdGhlIG5leHQgbGFiIHRvIGJyaW5nIGluIENlbnN1cyBzcGF0aWFsIGRhdGEuIEFuZCBjb25ncmF0dWxhdGlvbnMhIFlvdeKAmXZlIGp1c3QgZWFybmVkIGFub3RoZXIgYmFkZ2UuIEZhbnRhc3RpYyEKClwKCiFbdGlkeWNlbnN1cyBCYWRnZV0odGlkeWNlbnN1c19zdGlja2VyLnBuZykKClwKCiMgUmVhZGluZyBpbiBkYXRhCiMjIFBvbGljeU1hcAoKVG8gc2F2ZSB1cyB0aW1lLCBJ4oCZdmUgdXBsb2FkZWQgYSBQb2xpY3lNYXAgKFtsaW5rIHRvIC5jc3ZdKGh0dHBzOi8vZ2l0aHViLmNvbS9wamFtZXMtdWNkYXZpcy9TUEgyMTUvYmxvYi9tYWluL1BvbGljeU1hcCUyMERhdGElMjAyMDI1LTAzLTI3JTIwMTkyNTU1JTIwVVRDLmNzdikpIG9uIHRoZSBHaXRodWIgZm9yIHlvdSB0byB1c2UgaW4gdGhpcyBsYWIuIFNhdmUgdGhpcyBmaWxlIGluIHRoZSBzYW1lIGZvbGRlciB3aGVyZSB5b3VyIExhYiAyIFIgTWFya2Rvd24gZmlsZSByZXNpZGVzLiBUbyByZWFkIGluIGEgLmNzdiBmaWxlLCBmaXJzdCBtYWtlIHN1cmUgdGhhdCBSIGlzIHBvaW50ZWQgdG8gdGhlIGZvbGRlciB5b3Ugc2F2ZWQgeW91ciBkYXRhIGludG8uIFR5cGUgaW4gYGdldHdkKClgIHRvIGZpbmQgb3V0IHRoZSBjdXJyZW50IGRpcmVjdG9yeSBhbmQgYHNldHdkKCJkaXJlY3RvcnkgbmFtZSIpYCB0byBzZXQgdGhlIGRpcmVjdG9yeSB0byB0aGUgZm9sZGVyIGNvbnRhaW5pbmcgdGhlIGRhdGEuIAoKRnJvbSBhIE1hYyBsYXB0b3AsIEkgdHlwZSBpbiB0aGUgZm9sbG93aW5nIGNvbW1hbmQgdG8gc2V0IHRoZSBkaXJlY3RvcnkgdG8gdGhlIGZvbGRlciBjb250YWluaW5nIG15IGRhdGEuCgpgYGB7cn0Kc2V0d2QoIi9Vc2Vycy9wamFtZXMxL0Ryb3Bib3gvVUMgRGF2aXMgRm9sZGVycy9TUEggMjE1IEdJUyBhbmQgUHVibGljIEhlYWx0aC9HaXRodWJfV2Vic2l0ZS9TUEgyMTUvIikKYGBgCgpcCgpGb3IgYSBXaW5kb3dzIHN5c3RlbSwgeW91IGNhbiBmaW5kIHRoZSBwYXRod2F5IG9mIGEgZmlsZSBieSByaWdodCBjbGlja2luZyBvbiBpdCBhbmQgc2VsZWN0aW5nIFByb3BlcnRpZXMuIFlvdSB3aWxsIGZpbmQgdGhhdCBpbnN0ZWFkIG9mIGEgZm9yd2FyZCBzbGFzaCBsaWtlIGluIGEgTWFjLCBhIHdpbmRvd3MgcGF0aHdheSB3aWxsIGJlIGluZGljYXRlZCBieSBhIHNpbmdsZSBiYWNrIHNsYXNoIFwuIFIgZG9lc27igJl0IGxpa2UgdGhpcyBiZWNhdXNlIGl0IHRoaW5rcyBvZiBhIHNpbmdsZSBiYWNrIHNsYXNoIGFzIGFuIGVzY2FwZSBjaGFyYWN0ZXIuIFVzZSBpbnN0ZWFkIHR3byBiYWNrIHNsYXNoZXMgXFwKCmBgYHtyIGV2YWw9RkFMU0V9CnNldHdkKCJDOlxcVXNlcnNcXHBqYW1lc1xcRG9jdW1lbnRzXFxVQ0RcXFNQSDIxNVxcTGFic1xcTGFiIDMiKQpgYGAKb3IgYSBmb3J3YXJkIHNsYXNoIC8uCgpgYGB7ciBldmFsPUZBTFNFfQpzZXR3ZCgiQzovVXNlcnMvcGphbWVzL0RvY3VtZW50cy9VQ0QvU1BIMjE1L0xhYnMvTGFiIDMiKQpgYGAKCllvdSBjYW4gYWxzbyBtYW51YWxseSBzZXQgdGhlIHdvcmtpbmcgZGlyZWN0b3J5IGJ5IGNsaWNraW5nIG9uIFNlc3Npb24gLT4gU2V0IFdvcmtpbmcgRGlyZWN0b3J5IC0+IENob29zZSBEaXJlY3RvcnkgZnJvbSB0aGUgbWVudS4KClwKCk9uY2UgeW914oCZdmUgc2V0IHlvdXIgZGlyZWN0b3J5LCB1c2UgdGhlIGZ1bmN0aW9uIGByZWFkX2NzdigpYCwgd2hpY2ggaXMgYSBwYXJ0IG9mIHRoZSAqKnRpZHl2ZXJzZSoqIHBhY2thZ2UsIGFuZCBwbHVnIGluIHRoZSBuYW1lIG9mIHRoZSBmaWxlIGluIHF1b3RlcyBpbnNpZGUgdGhlIHBhcmVudGhlc2VzLiBNYWtlIHN1cmUgeW91IGluY2x1ZGUgdGhlICouY3N2KiBleHRlbnNpb24uCgpgYGB7cn0KY2EucG0gPC0gcmVhZF9jc3YoIlBvbGljeU1hcCBEYXRhIDIwMjUtMDMtMjcgMTkyNTU1IFVUQy5jc3YiLCBza2lwID0gMSkKYGBgCgpcCgpUaGUgb3B0aW9uIGBza2lwID0gMWAgdGVsbHMgUiB0byBza2lwIHRoZSBmaXJzdCByb3cgb2YgdGhlIGZpbGUgd2hlbiBicmluZ2luZyBpdCBpbi4gVGhpcyBpcyBkb25lIGJlY2F1c2UgdGhlcmUgYXJlIHR3byByb3dzIG9mIGNvbHVtbiBuYW1lcy4gVGhlIGZpcnN0IHJvdyBjb250YWlucyB0aGUgZXh0ZW5kZWQgdmVyc2lvbiwgd2hpbGUgdGhlIHNlY29uZCBpcyB0aGUgYWJyaWRnZWQgdmVyc2lvbi4gQWJvdmUgd2Uga2VlcCB0aGUgYWJyaWRnZWQgdmVyc2lvbi4KCllvdSBzaG91bGQgc2VlIGEgdGliYmxlICpjYS5wbSogcG9wIHVwIGluIHlvdXIgRW52aXJvbm1lbnQgd2luZG93ICh0b3AgcmlnaHQpLiBXaGF0IGRvZXMgb3VyIGRhdGEgc2V0IGxvb2sgbGlrZT8KCmBgYHtyfQpnbGltcHNlKGNhLnBtKQpgYGAKClwKCklmIHlvdSBsaWtlIHZpZXdpbmcgeW91ciBkYXRhIHRocm91Z2ggYW4gRXhjZWwgc3R5bGUgd29ya3NoZWV0LCB0eXBlIGluIGBWaWV3KGNhLnBtKWAsIGFuZCAqY2EucG0qIHNob3VsZCBwb3AgdXAgaW4gdGhlIHRvcCBsZWZ0IHdpbmRvdyBvZiB5b3VyIFIgU3R1ZGlvIGludGVyZmFjZS4KClwKCiMgTW9yZSBkYXRhIHdyYW5nbGluZwoKV2UgbGVhcm5lZCBhYm91dCB0aGUgdmFyaW91cyBkYXRhIHdyYW5nbGluZyByZWxhdGVkIGZ1bmN0aW9ucyBmcm9tIHRoZSAqKnRpZHl2ZXJzZSoqIHBhY2thZ2UgaW4gW0xhYiAxXShMYWIxXzIwMjYuaHRtbCkuIExldOKAmXMgZW1wbG95IHNvbWUgb2YgdGhvc2UgZnVuY3Rpb25zIHRvIGNyZWF0ZSBhIHNpbmdsZSBjb3VudHkgbGV2ZWwgZGF0YXNldCB0aGF0IGpvaW5zIHRoZSBkYXRhc2V0cyB3ZSBkb3dubG9hZGVkIGZyb20gdGhlIENlbnN1cyBBUEkgYW5kIFBvbGljeU1hcC4KCldlIGFyZSBnb2luZyB0byBjb21iaW5lIHRoZXNlIGRhdGFzZXRzIHVzaW5nIHRoZSBjb3VudHkgRklQUyBjb2Rlcy4gSW4gdGhlIENlbnN1cyBBUEkgYW5kIFBvbGljeU1hcCwgdGhlc2UgYXJlIGNvbnRhaW5lZCBpbiB0aGUgdmFyaWFibGVzIEdFT0lEIGFuZCBHZW9JRCwgcmVzcGVjdGl2ZWx5LiBMZXTigJlzIG1ha2Ugc3VyZSB0aGV5IGFyZSBpbiB0aGUgc2FtZSBjbGFzcy4KCmBgYHtyfQpjbGFzcyhjYS5wbSRHZW9JRCkKY2xhc3MoY2EkR0VPSUQpCmBgYAoKXAoKIyMgUGlwaW5nCgpPbmUgb2YgdGhlIGltcG9ydGFudCBpbm5vdmF0aW9ucyBmcm9tIHRoZSB0aWR5dmVyc2UgaXMgdGhlIHBpcGUgb3BlcmF0b3IgYCU+JWAuIFlvdSB1c2UgdGhlIHBpcGUgb3BlcmF0b3Igd2hlbiB5b3Ugd2FudCB0byBjb21iaW5lIG11bHRpcGxlIG9wZXJhdGlvbnMgaW50byBvbmUgbGluZSBvZiBjb250aW51b3VzIGNvZGUuIExldOKAmXMgY3JlYXRlIG91ciBmaW5hbCBkYXRhIG9iamVjdCAqY2Fjb3VudHkqIHVzaW5nIG91ciBicmFuZCBuZXcgZnJpZW5kIHRoZSBwaXBlLiBMZXQncyBwdXQgdG9nZXRoZXIgYSBsb3Qgb2Ygd2hhdCB3ZSd2ZSBkb25lIG92ZXIgdGhlIHBhc3QgZmV3IHdlZWtzIGludG8gb25lIHN0ZXAhIE5vdyB3ZSBhcmUgY29va2luZyB3aXRoIGdhcyEKCmBgYHtyfQpjYWNvdW50eSA8LSBjYSAlPiUgCiAgICAgIGxlZnRfam9pbihjYS5wbSwgYnkgPSBjKCJHRU9JRCIgPSAiR2VvSUQiKSkgJT4lCiAgICAgIG11dGF0ZShwd2hpdGUgPSBuaHdoaXRlRS90cG9wckUsIHBhc2lhbiA9IG5oYXNuRS90cG9wckUsIAogICAgICAgICAgICAgIHBibGFjayA9IG5oYmxrRS90cG9wckUsIHBoaXNwID0gaGlzcEUvdHBvcHJFLAogICAgICAgICAgICAgbWhpc3AgPSBjYXNlX3doZW4ocGhpc3AgPiAwLjUgfiAiTWFqb3JpdHkiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSB+ICJOb3QgTWFqb3JpdHkiKSkgJT4lCiAgICAgIHJlbmFtZShDb3VudHkgPSBHZW9JRF9OYW1lKSAlPiUKICAgICAgc2VsZWN0KEdFT0lELCBDb3VudHksIHB3aGl0ZSwgcGFzaWFuLCBwYmxhY2ssIHBoaXNwLCBtaGlzcCwgbWhoaW5jKQpnbGltcHNlKGNhY291bnR5KQpgYGAKCkxldOKAmXMgYnJlYWsgZG93biB3aGF0IHRoZSBwaXBlIGlzIGRvaW5nIGhlcmUuIEZpcnN0LCB5b3Ugc3RhcnQgb3V0IHdpdGggeW91ciBkYXRhc2V0IGNhLiBZb3Ug4oCccGlwZeKAnSB0aGF0IGludG8gdGhlIGNvbW1hbmQgYGxlZnRfam9pbigpYC4gTm90aWNlIHRoYXQgeW91IGRpZG7igJl0IGhhdmUgdG8gdHlwZSBpbiBjYSBpbnNpZGUgdGhhdCBjb21tYW5kIC0gYCU+JWAgcGlwZXMgdGhhdCBpbiBmb3IgeW91LiBUaGUgY29tbWFuZCBqb2lucyB0aGUgZGF0YSBvYmplY3QgKmNhLnBtKiB0byAqY2EqLiBUaGUgcmVzdWx0IG9mIHRoaXMgZnVuY3Rpb24gZ2V0cyBwaXBlZCBpbnRvIHRoZSBgbXV0YXRlKClgIGZ1bmN0aW9uLCB3aGljaCBjcmVhdGVzIHRoZSBwZXJjZW50IHJhY2UvZXRobmljaXR5IChmcm9tIHRoZSBDZW5zdXMgQVBJKSwgYW5kIG1ham9yaXR5IEhpc3BhbmljIHZhcmlhYmxlcy4gVGhpcyBnZXRzIHBpcGVkIGludG8gdGhlIGByZW5hbWUoKWAgZnVuY3Rpb24sIHdoaWNoIHJlbmFtZXMgdGhlIGFtYmlndW91cyB2YXJpYWJsZSBuYW1lICpHZW9JRF9OYW1lKiB0byB0aGUgbW9yZSBkZXNjcmlwdGl2ZSBuYW1lICpDb3VudHkqLiBUaGlzIHRoZW4gZ2V0cyBwaXBlZCBpbnRvIHRoZSBmaW5hbCBmdW5jdGlvbiwgYHNlbGVjdCgpYCwgd2hpY2gga2VlcHMgdGhlIG5lY2Vzc2FyeSB2YXJpYWJsZXMuIEZpbmFsbHksIHRoZSBjb2RlIHNhdmVzIHRoZSByZXN1bHQgaW50byBjYWNvdW50eSB3aGljaCB3ZSBkZXNpZ25hdGVkIGF0IHRoZSBiZWdpbm5pbmcgd2l0aCB0aGUgYXJyb3cgb3BlcmF0b3IuCgpQaXBpbmcgbWFrZXMgY29kZSBjbGVhcmVyLCBhbmQgc2ltdWx0YW5lb3VzbHkgZ2V0cyByaWQgb2YgdGhlIG5lZWQgdG8gZGVmaW5lIGFueSBpbnRlcm1lZGlhdGUgb2JqZWN0cyB0aGF0IHlvdSB3b3VsZCBoYXZlIG5lZWRlZCB0byBrZWVwIHRyYWNrIG9mIHdoaWxlIHJlYWRpbmcgdGhlIGNvZGUuIFBJUEUsIFBpcGUsIGFuZCBwaXBlIHdoZW5ldmVyIHlvdSBjYW4uIFdlIG5lZWQgc29tZSBzdGlua2luIGJhZGdlcyEKClwKCiFbcGlwZSBCYWRnZSFdKHBpcGUucG5nKQoKXAoKIyMgU2F2aW5nIGRhdGEKCklmIHlvdSB3YW50IHRvIHNhdmUgeW91ciBkYXRhIGZyYW1lIG9yIHRpYmJsZSBhcyBhIGNzdiBmaWxlIG9uIHlvdXIgaGFyZCBkcml2ZSwgdXNlIHRoZSBjb21tYW5kIGB3cml0ZV9jc3YoKWAuIEJlZm9yZSB5b3Ugc2F2ZSBhIGZpbGUsIG1ha2Ugc3VyZSBSIGlzIHBvaW50ZWQgdG8gdGhlIGFwcHJvcHJpYXRlIGZvbGRlciBvbiB5b3VyIGhhcmQgZHJpdmUgYnkgdXNpbmcgdGhlIGZ1bmN0aW9uIGBnZXR3ZCgpYC4gSWYgaXTigJlzIG5vdCBwb2ludGVkIHRvIHRoZSByaWdodCBmb2xkZXIsIHVzZSB0aGUgZnVuY3Rpb24gYHNldHdkKClgIHRvIHNldCB0aGUgYXBwcm9wcmlhdGUgd29ya2luZyBkaXJlY3RvcnkuCgpgYGB7cn0Kd3JpdGVfY3N2KGNhY291bnR5LCAibGFiMl9maWxlLmNzdiIpCmBgYAoKVGhlIGZpcnN0IGFyZ3VtZW50IGlzIHRoZSBuYW1lIG9mIHRoZSBSIG9iamVjdCB5b3Ugd2FudCB0byBzYXZlLiBUaGUgc2Vjb25kIGFyZ3VtZW50IGlzIHRoZSBuYW1lIG9mIHRoZSBjc3YgZmlsZSBpbiBxdW90ZXMuIE1ha2Ugc3VyZSB0byBhZGQgdGhlIC5jc3YgZXh0ZW5zaW9uLiBUaGUgZmlsZSBpcyBzYXZlZCBpbiB5b3VyIGN1cnJlbnQgd29ya2luZyBkaXJlY3RvcnkuCgpcCgojIEV4cGxvcmF0b3J5IGRhdGEgYW5hbHlzaXMKClRoZSBmdW5jdGlvbnMgYWJvdmUgaGVscCB1cyBicmluZyBpbiBhbmQgY2xlYW4gZGF0YS4gVGhlIG5leHQgc2V0IG9mIGZ1bmN0aW9ucyBjb3ZlcmVkIGluIHRoaXMgc2VjdGlvbiB3aWxsIGhlbHAgdXMgc3VtbWFyaXplIHRoZSBkYXRhLiBEYXRhIHJlZmVyIHRvIHBpZWNlcyBvZiBpbmZvcm1hdGlvbiB0aGF0IGRlc2NyaWJlIGEgc3RhdHVzIG9yIGEgbWVhc3VyZSBvZiBtYWduaXR1ZGUuIEEgdmFyaWFibGUgaXMgYSBzZXQgb2Ygb2JzZXJ2YXRpb25zIG9uIGEgcGFydGljdWxhciBjaGFyYWN0ZXJpc3RpYy4gVGhlIGRpc3RyaWJ1dGlvbiBvZiBhIHZhcmlhYmxlIGlzIGEgbGlzdGluZyBzaG93aW5nIGFsbCB0aGUgcG9zc2libGUgdmFsdWVzIG9mIHRoZSBkYXRhIGZvciB0aGF0IHZhcmlhYmxlIGFuZCBob3cgb2Z0ZW4gdGhleSBvY2N1ci4gRXhwbG9yYXRvcnkgRGF0YSBBbmFseXNpcyAoRURBKSBlbmNvbXBhc3NlcyBhIHNldCBvZiBtZXRob2RzIChzb21lIHdvdWxkIHNheSBhIGZyYW1ld29yayBvciBwZXJzcGVjdGl2ZSkgZm9yIHN1bW1hcml6aW5nIGEgdmFyaWFibGXigJlzIGRpc3RyaWJ1dGlvbiwgYW5kIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGUgZGlzdHJpYnV0aW9ucyBvZiB0d28gb3IgbW9yZSB2YXJpYWJsZXMuIFdlIHdpbGwgY292ZXIgdHdvIGdlbmVyYWwgYXBwcm9hY2hlcyB0byBzdW1tYXJpemluZyB5b3VyIGRhdGE6IGRlc2NyaXB0aXZlIHN0YXRpc3RpY3MgYW5kIHZpc3VhbGl6YXRpb24gdmlhIGdyYXBocyBhbmQgY2hhcnRzLgoKXAoKIyMgRGVzY3JpcHRpdmUgc3RhdGlzdGljcwoKV2hlbiBkZXNjcmliaW5nIGEgZGlzdHJpYnV0aW9uLCB5b3VyIHF1YW50aXRhdGl2ZSBtZXNzYWdlIGlzIG9mdGVuIGJlc3QgY29tbXVuaWNhdGVkIGJ5IHJlZHVjaW5nIGRhdGEgdG8gYSBmZXcgc3VtbWFyeSBudW1iZXJzLiBUaGVzZSBudW1iZXJzIGFyZSBtZWFudCB0byBzdW1tYXJpemUgdGhlIOKAnHR5cGljYWzigJ0gdmFsdWUgaW4gdGhlIGRpc3RyaWJ1dGlvbiAoZS5nLiwgbWVhbiwgbWVkaWFuLCBtb2RlKSBhbmQgdGhlIHZhcmlhdGlvbiBvciDigJxzcHJlYWTigJ0gaW4gdGhlIGRpc3RyaWJ1dGlvbiAoZS5nLiwgbWluaW11bS9tYXhpbXVtLCBpbnRlcnF1YXJ0aWxlIHJhbmdlLCBzdGFuZGFyZCBkZXZpYXRpb24pLiBUaGVzZSBzdW1tYXJ5IG51bWJlcnMgYXJlIGtub3duIGFzIGRlc2NyaXB0aXZlIHN0YXRpc3RpY3MuCgpXZSBjYW4gdXNlIHRoZSBmdW5jdGlvbiBzdW1tYXJpemUoKSB0byBnZXQgZGVzY3JpcHRpdmUgc3RhdGlzdGljcyBvZiBvdXIgZGF0YS4gRm9yIGV4YW1wbGUsIGxldOKAmXMgY2FsY3VsYXRlIHRoZSBtZWFuIGhvdXNlaG9sZCBpbmNvbWUgaW4gQ2FsaWZvcm5pYSBjb3VudGllcy4gVGhlIGZpcnN0IGFyZ3VtZW50IGluc2lkZSBzdW1tYXJpemUoKSBpcyB0aGUgZGF0YSBvYmplY3QgY2Fjb3VudHkgYW5kIHRoZSBzZWNvbmQgYXJndW1lbnQgaXMgdGhlIGZ1bmN0aW9uIGNhbGN1bGF0aW5nIHRoZSBzcGVjaWZpYyBzdW1tYXJ5IHN0YXRpc3RpYywgaW4gdGhpcyBjYXNlIG1lYW4oKS4KCmBgYHtyfQpjYWNvdW50eSAlPiUKICBzdW1tYXJpemUoTWVhbiA9IG1lYW4obWhoaW5jKSkKYGBgCgpUaGUgYXZlcmFnZSBjb3VudHkgbWVkaWFuIGhvdXNlaG9sZCBpbmNvbWUgaXMgJDg3LDAwMS4gSWYgdGhlIHZhcmlhYmxlICptaGhpbmMqIGNvbnRhaW5lZCBtaXNzaW5nIHZhbHVlcywgd2Ugd291bGQgaGF2ZSBnb3R0ZW4gKk5BKiBhcyBhIHJlc3VsdC4gVG8gb21pdCBtaXNzaW5nIHZhbHVlcyBmcm9tIHRoZSBjYWxjdWxhdGlvbiwgeW91IG5lZWQgdG8gYWRkIGBybSA9IFRSVUVgIHRvIGBtZWFuKClgLgoKV2UgY2FuIGNhbGN1bGF0ZSBtb3JlIHRoYW4gb25lIHN1bW1hcnkgc3RhdGlzdGljIHdpdGhpbiBgc3VtbWFyaXplKClgLiBXaGF0IGlzIHRoZSBzcHJlYWQgb2YgdGhlIGRpc3RyaWJ1dGlvbj8gV2UgY2FuIGFkZCB0byBgc3VtbWFyaXplKClgIHRoZSBmdW5jdGlvbiBgc2QoKWAgdG8gY2FsY3VsYXRlIHRoZSBzdGFuZGFyZCBkZXZpYXRpb24uClwKCmBgYHtyfQpjYWNvdW50eSAlPiUKICBzdW1tYXJpemUoTWVhbiA9IG1lYW4obWhoaW5jKSwgU0QgPSBzZChtaGhpbmMpKQpgYGAKClwKCkRvZXMgdGhlIGF2ZXJhZ2UgaW5jb21lIGRpZmZlciBieSBDYWxpZm9ybmlhIHJlZ2lvbj8gRmlyc3QsIGxldOKAmXMgY3JlYXRlIGEgbmV3IHZhcmlhYmxlIHJlZ2lvbiBkZXNpZ25hdGluZyBlYWNoIGNvdW50eSBhcyBCYXkgQXJlYSwgU291dGhlcm4gQ2FsaWZvcm5pYSwgQ2VudHJhbCBWYWxsZXksIENhcGl0YWwgUmVnaW9uIGFuZCB0aGUgUmVzdCBvZiBDYWxpZm9ybmlhIHVzaW5nIHRoZSBgY2FzZV93aGVuKClgIGZ1bmN0aW9uIHdpdGhpbiB0aGUgYG11dGF0ZSgpYCBmdW5jdGlvbi4KCmBgYHtyfQpjYWNvdW50eSA8LSBjYWNvdW50eSAlPiUKICAgIG11dGF0ZShyZWdpb24gPSBjYXNlX3doZW4oQ291bnR5ID09ICJTb25vbWEiIHwgQ291bnR5ID09ICJOYXBhIiB8IAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDb3VudHkgPT0gIlNvbGFubyIgfCBDb3VudHkgPT0gIk1hcmluIiB8IAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDb3VudHkgPT0gIkNvbnRyYSBDb3N0YSIgfCBDb3VudHkgPT0gIlNhbiBGcmFuY2lzY28iIHwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ291bnR5ID09ICJTYW4gTWF0ZW8iIHwgQ291bnR5ID09ICJBbGFtZWRhIiB8IAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDb3VudHkgPT0gIlNhbnRhIENsYXJhIiB+ICJCYXkgQXJlYSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIENvdW50eSA9PSAiSW1wZXJpYWwiIHwgQ291bnR5ID09ICJMb3MgQW5nZWxlcyIgfCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ291bnR5ID09ICJPcmFuZ2UiIHwgQ291bnR5ID09ICJSaXZlcnNpZGUiIHwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ291bnR5ID09ICJTYW4gRGllZ28iIHwgQ291bnR5ID09ICJTYW4gQmVybmFyZGlubyIgfAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDb3VudHkgPT0gIlZlbnR1cmEiIH4gIlNvdXRoZXJuIENhbGlmb3JuaWEiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDb3VudHkgPT0gIkZyZXNubyIgfCBDb3VudHkgPT0gIk1hZGVyYSIgfCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ291bnR5ID09ICJNYXJpcG9zYSIgfCBDb3VudHkgPT0gIk1lcmNlZCIgfCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ291bnR5ID09ICJUdWxhcmUiIHwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIENvdW50eSA9PSAiS2luZ3MiIH4gIkNlbnRyYWwgVmFsbGV5IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ291bnR5ID09ICJBbHBpbmUiIHwgQ291bnR5ID09ICJDb2x1c2EiIHwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ291bnR5ID09ICJFbCBEb3JhZG8iIHwgQ291bnR5ID09ICJHbGVubiIgfAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDb3VudHkgPT0gIlBsYWNlciIgfCBDb3VudHkgPT0gIlNhY3JhbWVudG8iIHwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ291bnR5ID09ICJTdXR0ZXIiIHwgQ291bnR5ID09ICJZb2xvIiB8CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIENvdW50eSA9PSAiWXViYSIgfiAiQ2FwaXRhbCBSZWdpb24iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFIH4gIlJlc3QiKSkKYGBgCgpcCgpOZXh0LCB3ZSBuZWVkIHRvIHBhaXIgYHN1bW1hcml6ZSgpYCB3aXRoIHRoZSBmdW5jdGlvbiBgZ3JvdXBfYnkoKWAuIFRoZSBmdW5jdGlvbiBgZ3JvdXBfYnkoKWAgdGVsbHMgUiB0byBydW4gc3Vic2VxdWVudCBmdW5jdGlvbnMgb24gdGhlIGRhdGEgb2JqZWN0IGJ5IGEgZ3JvdXAgY2hhcmFjdGVyaXN0aWMgKHN1Y2ggYXMgZ2VuZGVyLCBlZHVjYXRpb25hbCBhdHRhaW5tZW50LCBvciBpbiB0aGlzIGNhc2UsIHJlZ2lvbikuIFdl4oCZbGwgbmVlZCB0byB1c2Ugb3VyIG5ldyBiZXN0IGZyaWVuZCAlPiUgdG8gYWNjb21wbGlzaCB0aGlzIHRhc2suCgpgYGB7cn0KY2Fjb3VudHkgJT4lCiAgZ3JvdXBfYnkocmVnaW9uKSAlPiUKICBzdW1tYXJpemUoTWVhbiA9IG1lYW4obWhoaW5jKSkKYGBgCgpUaGUgZmlyc3QgcGlwZSBzZW5kcyBjYWNvdW50eSBpbnRvIHRoZSBmdW5jdGlvbiBgZ3JvdXBfYnkoKWAsIHdoaWNoIHRlbGxzIFIgdG8gZ3JvdXAgKmNhY291bnR5KiBieSB0aGUgdmFyaWFibGUgKnJlZ2lvbiouCgpIb3cgZG8geW91IGtub3cgdGhlIHRpYmJsZSBpcyBncm91cGVkPyBCZWNhdXNlIGl0IHRlbGxzIHlvdSEKCmBgYHtyfQpjYWNvdW50eSAlPiUKICBncm91cF9ieShyZWdpb24pIApgYGAKClwKClRoZSBzZWNvbmQgcGlwZSB0YWtlcyB0aGlzIGdyb3VwZWQgZGF0YXNldCBhbmQgc2VuZHMgaXQgaW50byB0aGUgYHN1bW1hcml6ZSgpYCBjb21tYW5kLCB3aGljaCBjYWxjdWxhdGVzIHRoZSBtZWFuIGluY29tZSAoYnkgcmVnaW9uLCBiZWNhdXNlIHRoZSBkYXRhc2V0IGlzIGdyb3VwZWQgYnkgcmVnaW9uKS4KClRvIGdldCB0aGUgbWVhbiwgbWVkaWFuIGFuZCBzdGFuZGFyZCBkZXZpYXRpb24gb2YgbWVkaWFuIGluY29tZSwgaXRzIGNvcnJlbGF0aW9uIHdpdGggcGVyY2VudCBIaXNwYW5pYywgYW5kIGdpdmUgY29sdW1uIGxhYmVscyBmb3IgdGhlIHZhcmlhYmxlcyBpbiB0aGUgcmVzdWx0aW5nIHN1bW1hcnkgdGFibGUsIHdlIHR5cGUgaW46CgpgYGB7cn0KY2Fjb3VudHkgJT4lCiAgZ3JvdXBfYnkocmVnaW9uKSAlPiUKICBzdW1tYXJpemUoTWVhbiA9IG1lYW4obWhoaW5jKSwKICAgICAgICAgICAgTWVkaWFuID0gbWVkaWFuKG1oaGluYyksCiAgICAgICAgICAgIFNEID0gc2QobWhoaW5jKSwKICAgICAgICAgICAgQ29ycmVsYXRpb24gPSBjb3IobWhoaW5jLCBwaGlzcCkpCmBgYAoKXAoKVGhlIHZhcmlhYmxlICptaGhpbmMqIGlzIG51bWVyaWMuIEhvdyBkbyB3ZSBzdW1tYXJpemUgY2F0ZWdvcmljYWwgdmFyaWFibGVzPyBXZSB1c3VhbGx5IHN1bW1hcml6ZSBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgYnkgZXhhbWluaW5nIGEgZnJlcXVlbmN5IHRhYmxlLiBUbyBnZXQgdGhlIHBlcmNlbnQgb2YgY291bnRpZXMgdGhhdCBoYXZlIGEgbWFqb3JpdHkgSGlzcGFuaWMgcG9wdWxhdGlvbiBtaGlzcCwgeW914oCZbGwgbmVlZCB0byBjb21iaW5lIHRoZSBmdW5jdGlvbnMgKmdyb3VwX2J5KCkqLCAqc3VtbWFyaXplKCkqIGFuZCAqbXV0YXRlKCkqIHVzaW5nIGAlPiVgLgoKYGBge3J9CmNhY291bnR5ICU+JQogIGdyb3VwX2J5KG1oaXNwKSAlPiUKICBzdW1tYXJpemUobiA9IG4oKSkgJT4lCiAgbXV0YXRlKGZyZXEgPSBuIC8gc3VtKG4pKQpgYGAKClwKClRoZSBjb2RlIGBncm91cF9ieShtaGlzcClgIHNlcGFyYXRlcyB0aGUgY291bnRpZXMgYnkgdGhlIGNhdGVnb3JpZXMgb2YgbWhpc3AgKE1ham9yaXR5LCBOb3QgTWFqb3JpdHkpLiBXZSB0aGVuIHVzZWQgYHN1bW1hcml6ZSgpYCB0byBjb3VudCB0aGUgbnVtYmVyIG9mIGNvdW50aWVzIHRoYXQgYXJlIE1ham9yaXR5IGFuZCBOb3QgTWFqb3JpdHkuIFRoZSBmdW5jdGlvbiB0byBnZXQgYSBjb3VudCBpcyBgbigpYCwgYW5kIHdlIHNhdmVkIHRoaXMgY291bnQgaW4gYSB2YXJpYWJsZSBuYW1lZCAqbiouIE5leHQsIHdlIHVzZWQgYG11dGF0ZSgpYCBvbiB0aGlzIHRhYmxlIHRvIGdldCB0aGUgcHJvcG9ydGlvbiBvZiBjb3VudGllcyBieSBNYWpvcml0eSBIaXNwYW5pYyBkZXNpZ25hdGlvbi4gVGhlIGNvZGUgYHN1bShuKWAgYWRkcyB0aGUgdmFsdWVzIG9mICpuKi4gV2UgdGhlbiBkaXZpZGUgdGhlIHZhbHVlIG9mIGVhY2ggbiBieSB0aGlzIHN1bS4gVGhhdCB5aWVsZHMgdGhlIGZpbmFsIGZyZXF1ZW5jeSB0YWJsZS4KCkluc3RlYWQgb2YgY2FsY3VsYXRpbmcgZGVzY3JpcHRpdmUgc3RhdGlzdGljcyBvbmUgYXQgYSB0aW1lIHVzaW5nIGBzdW1tYXJpemUoKWAsIHlvdSBjYW4gb2J0YWluIGEgc2V0IG9mIHN1bW1hcnkgc3RhdGlzdGljcyBmb3Igb25lIG9yIGFsbCB0aGUgbnVtZXJpYyB2YXJpYWJsZXMgaW4geW91ciBkYXRhc2V0IHVzaW5nIHRoZSBgc3VtbWFyeSgpYCBmdW5jdGlvbi4KCmBgYHtyfQpzdW1tYXJ5KGNhY291bnR5KQpgYGAKClwKCiMjIFRhYmxlcyBmb3IgcHJlc2VudGF0aW9uCgpUaGUgb3V0cHV0IGZyb20gdGhlIGRlc2NyaXB0aXZlIHN0YXRpc3RpY3Mgd2XigJl2ZSByYW4gc28gZmFyIGlzIG5vdCBwcmVzZW50YXRpb24gcmVhZHkuIEZvciBleGFtcGxlLCB0YWtpbmcgYSBzY3JlZW5zaG90IG9mIHRoZSBmb2xsb3dpbmcgcmVzdWx0cyB0YWJsZSBwcm9kdWNlcyB1bm5lY2Vzc2FyeSBpbmZvcm1hdGlvbiB0aGF0IGlzIGNvbmZ1c2luZyBhbmQgbWVzc3kuCgpgYGB7cn0KY2Fjb3VudHkgJT4lCiAgZ3JvdXBfYnkocmVnaW9uKSAlPiUKICBzdW1tYXJpemUoTWVhbiA9IG1lYW4obWhoaW5jKSwKICAgICAgICAgICAgTWVkaWFuID0gbWVkaWFuKG1oaGluYyksCiAgICAgICAgICAgIFNEID0gc2QobWhoaW5jKSwKICAgICAgICAgICAgQ29ycmVsYXRpb24gPSBjb3IobWhoaW5jLCBwaGlzcCkpCmBgYAoKRnVydGhlcm1vcmUsIHlvdSB3b3VsZCBsaWtlIHRvIHNob3cgYSB0YWJsZSwgc2F5LCBpbiBhIG1hbnVzY3JpcHQgdGhhdCBkb2VzIG5vdCByZXF1aXJlIHlvdSB0byB0YWtlIGEgc2NyZWVuc2hvdCBvciBjb3B5aW5nIGFuZCBwYXN0aW5nIGludG8gRXhjZWwsIGJ1dCBpbnN0ZWFkIGNhbiBiZSBwcm9kdWNlZCB2aWEgY29kZSwgdGhhdCB3YXkgaXQgY2FuIGJlIGZpeGVkIGlmIHRoZXJlIGlzIGFuIGlzc3VlLCBhbmQgaXMgcmVwcm9kdWNpYmxlLgoKT25lIHdheSBvZiBwcm9kdWNpbmcgcHJlc2VudGF0aW9uIHRhYmxlcyBpbiBSIGlzIHRocm91Z2ggdGhlICoqZmxleHRhYmxlKiogcGFja2FnZS4gRmlyc3QsIHlvdSB3aWxsIG5lZWQgdG8gc2F2ZSB0aGUgdGliYmxlIG9yIGRhdGEgZnJhbWUgb2YgcmVzdWx0cyBpbnRvIGFuIG9iamVjdC4gRm9yIGV4YW1wbGUsIGxldOKAmXMgc2F2ZSB0aGUgYWJvdmUgcmVzdWx0cyBpbnRvIGFuIG9iamVjdCBuYW1lZCAqcmVnaW9uLnN1bW1hcnkqCgpgYGB7cn0KcmVnaW9uLnN1bW1hcnkgPC0gY2Fjb3VudHkgJT4lCiAgZ3JvdXBfYnkocmVnaW9uKSAlPiUKICBzdW1tYXJpemUoTWVhbiA9IG1lYW4obWhoaW5jKSwKICAgICAgICAgICAgTWVkaWFuID0gbWVkaWFuKG1oaGluYyksCiAgICAgICAgICAgIFNEID0gc2QobWhoaW5jKSwKICAgICAgICAgICAgQ29ycmVsYXRpb24gPSBjb3IobWhoaW5jLCBwaGlzcCkpCmBgYAoKWW91IHRoZW4gaW5wdXQgdGhlIG9iamVjdCBpbnRvIHRoZSBmdW5jdGlvbiBgZmxleHRhYmxlKClgLiBTYXZlIGl0IGludG8gYW4gb2JqZWN0IGNhbGxlZCAqbXlfdGFibGUqCgpgYGB7cn0KbXlfdGFibGUgPC0gZmxleHRhYmxlKHJlZ2lvbi5zdW1tYXJ5KQpteV90YWJsZQpgYGAKClwKCllvdSBzaG91bGQgc2VlIGEgcmVsYXRpdmVseSBjbGVhbiB0YWJsZSBwb3AgdXAgZWl0aGVyIGluIHlvdXIgY29uc29sZSBvciBWaWV3ZXIgd2luZG93LgoKXAoKV2hhdCBraW5kIG9mIG9iamVjdCBpcyAqbXlfdGFibGUqPwpgYGB7cn0KY2xhc3MobXlfdGFibGUpCmBgYAoKXAoKQWZ0ZXIgZG9pbmcgdGhpcywgd2UgY2FuIHByb2dyZXNzaXZlbHkgcGlwZSB0aGUgKm15X3RhYmxlKiBvYmplY3QgdGhyb3VnaCBtb3JlICoqZmxleHRhYmxlKiogZm9ybWF0dGluZyBmdW5jdGlvbnMuIEZvciBleGFtcGxlLCB5b3UgY2FuIGNoYW5nZSB0aGUgY29sdW1uIGhlYWRlciBuYW1lcyB1c2luZyB0aGUgZnVuY3Rpb24gYHNldF9oZWFkZXJfbGFiZWxzKClgIGFuZCBjZW50ZXIgdGhlIGhlYWRlciBuYW1lcyB1c2luZyB0aGUgZnVuY3Rpb24gYGFsaWduKClgLgoKYGBge3J9Cm15X3RhYmxlIDwtIG15X3RhYmxlICU+JQogICAgICAgICAgc2V0X2hlYWRlcl9sYWJlbHMoCiAgICAgICAgICAgIHJlZ2lvbiA9ICJSZWdpb24iLAogICAgICAgICAgICBNZWFuID0gIk1lYW4iLAogICAgICAgICAgICBNZWRpYW4gPSAiTWVkaWFuIiwKICAgICAgICAgICAgU0QgPSAiU3RhbmRhcmQgRGV2aWF0aW9uIiwKICAgICAgICAgICAgQ29ycmVsYXRpb24gPSAiQ29ycmVsYXRpb24iKSAlPiUKICAgICAgICAgICAgICBmbGV4dGFibGU6OmFsaWduKGFsaWduID0gImNlbnRlciIsIHBhcnQgPSAiYWxsIikKCm15X3RhYmxlCmBgYAoKV2VsbCBkb2Vzbid0IHRoYXQgbG9vayBzcGlmZnkhIFRoZXJlIGFyZSBhIHNsZXcgb2Ygb3B0aW9ucyBmb3IgZm9ybWF0dGluZyB5b3VyIHRhYmxlLCBpbmNsdWRpbmcgYWRkaW5nIGZvb3Rub3RlcywgYm9yZGVycywgc2hhZGUgYW5kIG90aGVyIGZlYXR1cmVzLiBDaGVjayBvdXQgdGhpcyBbdXNlZnVsIHR1dG9yaWFsXShodHRwczovL2FyZGF0YS1mci5naXRodWIuaW8vZmxleHRhYmxlLWJvb2svKSBmb3IgYW4gZXhwbGFuYXRpb24gb2Ygc29tZSBvZiB0aGVzZSBmZWF0dXJlcy4KCk9uY2UgeW914oCZcmUgZG9uZSBmb3JtYXR0aW5nIHlvdXIgdGFibGUsIHlvdSBjYW4gdGhlbiBleHBvcnQgaXQgdG8gV29yZCwgUG93ZXJQb2ludCBvciBIVE1MIG9yIGFzIGFuIGltYWdlIChQTkcpIGZpbGVzLiBUbyBkbyB0aGlzLCB1c2Ugb25lIG9mIHRoZSBmb2xsb3dpbmcgZnVuY3Rpb25zOiBgc2F2ZV9hc19kb2N4KClgLCBgc2F2ZV9hc19wcHR4KClgLCBgc2F2ZV9hc19pbWFnZSgpYCwgYW5kIGBzYXZlX2FzX2h0bWwoKWAuIAoKVXNlIHRoZSBgc2F2ZV9hc19pbWFnZSgpYCBmdW5jdGlvbiB0byBzYXZlIHlvdXIgdGFibGUgYXMgYW4gaW1hZ2UuCgpgYGB7cn0Kc2F2ZV9hc19pbWFnZShteV90YWJsZSwgcGF0aCA9ICJyZWdfaW5jb21lLnBuZyIpCmBgYAoKWW91IGZpcnN0IHB1dCBpbiB0aGUgdGFibGUgbXlfdGFibGUsIGFuZCBzZXQgdGhlIGZpbGUgbmFtZSB3aXRoIHRoZSAucG5nIGV4dGVuc2lvbi4gQ2hlY2sgeW91ciB3b3JraW5nIGRpcmVjdG9yeS4gWW91IHNob3VsZCBzZWUgdGhlIGZpbGUgKnJlZ19pbmNvbWUucG5nKi4KClwKCiMjIERhdGEgdmlzdWFsaXphdGlvbgoKQW5vdGhlciB3YXkgb2Ygc3VtbWFyaXppbmcgdmFyaWFibGVzIGFuZCB0aGVpciByZWxhdGlvbnNoaXBzIGlzIHRocm91Z2ggZ3JhcGhzIGFuZCBjaGFydHMuIFRoZSBtYWluIHBhY2thZ2UgZm9yIFIgZ3JhcGhpbmcgaXMgKmdncGxvdDIqIHdoaWNoIGlzIGEgcGFydCBvZiB0aGUgKnRpZHl2ZXJzZSogcGFja2FnZS4gVGhlIGdyYXBoaW5nIGZ1bmN0aW9uIGlzIGBnZ3Bsb3QoKWAgYW5kIGl0IHRha2VzIG9uIHRoZSBiYXNpYyB0ZW1wbGF0ZQpgYGB7ciwgZXZhbD1GQUxTRX0KZ2dwbG90KGRhdGEgPSA8REFUQT4pICsKICAgICAgPEdFT01fRlVOQ1RJT04+KG1hcHBpbmcgPSBhZXMoeCwgeSkpICsKICAgICAgPE9QVElPTlM+KCkKYGBgCgpcCgoxLiBgZ2dwbG90KClgIGlzIHRoZSBiYXNlIGZ1bmN0aW9uIHdoZXJlIHlvdSBzcGVjaWZ5IHlvdXIgZGF0YXNldCB1c2luZyB0aGUgZGF0YSA9IDxEQVRBPiBhcmd1bWVudC4KMi4gWW91IHRoZW4gbmVlZCB0byBidWlsZCBvbiB0aGlzIGJhc2UgYnkgdXNpbmcgdGhlIHBsdXMgb3BlcmF0b3IgKyBhbmQgPEdFT01fRlVOQ1RJT04+KCkgd2hlcmUgPEdFT01fRlVOQ1RJT04+KCkgaXMgYSB1bmlxdWUgZ2VvbSBmdW5jdGlvbiBpbmRpY2F0aW5nIHRoZSB0eXBlIG9mIGdyYXBoIHlvdSB3YW50IHRvIHBsb3QuIEVhY2ggdW5pcXVlIGZ1bmN0aW9uIGhhcyBpdHMgdW5pcXVlIHNldCBvZiBtYXBwaW5nIGFyZ3VtZW50cyB3aGljaCB5b3Ugc3BlY2lmeSB1c2luZyB0aGUgbWFwcGluZyA9IGFlcygpIGFyZ3VtZW50LiBDaGFydHMgYW5kIGdyYXBocyBoYXZlIGFuIHgtYXhpcywgeS1heGlzLCBvciBib3RoLiBDaGVjayB0aGlzIGdncGxvdCBjaGVhdCBzaGVldCBmb3IgYWxsIHBvc3NpYmxlIGdlb21zLgozLiBgPE9QVElPTlM+KClgIGFyZSBhIHNldCBvZiBmdW5jdGlvbnMgeW91IGNhbiBzcGVjaWZ5IHRvIGNoYW5nZSB0aGUgbG9vayBvZiB0aGUgZ3JhcGgsIGZvciBleGFtcGxlIHJlbGFiZWxpbmcgdGhlIGF4ZXMgb3IgYWRkaW5nIGEgdGl0bGUuCgpUaGUgYmFzaWMgaWRlYSBpcyB0aGF0IGEgZ2dwbG90IGdyYXBoaWMgbGF5ZXJzIGdlb21ldHJpYyBvYmplY3RzIChjaXJjbGVzLCBsaW5lcywgZXRjKSwgdGhlbWVzLCBhbmQgc2NhbGVzIG9uIHRvcCBvZiBkYXRhLgoKWW91IGZpcnN0IHN0YXJ0IG91dCB3aXRoIHRoZSBiYXNlIGxheWVyLiBJdCByZXByZXNlbnRzIHRoZSBlbXB0eSAqZ2dwbG90KiBsYXllciBkZWZpbmVkIGJ5IHRoZSBgZ2dwbG90KClgIGZ1bmN0aW9uLgpgYGB7cn0KY2Fjb3VudHkgJT4lCiAgZ2dwbG90KCkKYGBgCgpXZSBnZXQgYW4gZW1wdHkgcGxvdC4gV2UgaGF2ZW7igJl0IHRvbGQgYGdncGxvdCgpYCB3aGF0IHR5cGUgb2YgZ2VvbWV0cmljIG9iamVjdChzKSB3ZSB3YW50IHRvIHBsb3QsIG5vciBob3cgdGhlIHZhcmlhYmxlcyBzaG91bGQgYmUgbWFwcGVkIHRvIHRoZSBnZW9tZXRyaWMgb2JqZWN0cywgc28gd2UganVzdCBoYXZlIGEgYmxhbmsgcGxvdC4gV2UgaGF2ZSBgZ2VvbXNgIHRvIHBhaW50IHRoZSBibGFuayBjYW52YXMuCgpGcm9tIGhlcmUsIHdlIGFkZCBhIOKAnGBnZW9tYOKAnSBsYXllciB0byB0aGUgZ2dwbG90IG9iamVjdC4gTGF5ZXJzIGFyZSBhZGRlZCB0byBnZ3Bsb3Qgb2JqZWN0cyB1c2luZyBgK2AsIGluc3RlYWQgb2YgYCU+JWAsIHNpbmNlIHlvdSBhcmUgbm90IGV4cGxpY2l0bHkgcGlwaW5nIGFuIG9iamVjdCBpbnRvIGVhY2ggc3Vic2VxdWVudCBsYXllciwgYnV0IGFkZGluZyBsYXllcnMgb24gdG9wIG9mIG9uZSBhbm90aGVyLiBFYWNoIGBnZW9tYCBpcyBhc3NvY2lhdGVkIHdpdGggYSBzcGVjaWZpYyB0eXBlIG9mIGdyYXBoLgoKTGV04oCZcyBnbyB0aHJvdWdoIHNvbWUgb2YgdGhlIG1vcmUgY29tbW9uIGFuZCBwb3B1bGFyIGdyYXBocyBmb3IgdmlzdWFsaXppbmcgeW91ciBkYXRhLgoKXAoKIyMjIEhpc3RvZ3JhbQoKQSB0eXBpY2FsIHZpc3VhbCBmb3Igc3VtbWFyaXppbmcgYSBzaW5nbGUgbnVtZXJpYyB2YXJpYWJsZSBpcyBhIGhpc3RvZ3JhbS4gVG8gY3JlYXRlIGEgaGlzdG9ncmFtLCB1c2UgYGdlb21faGlzdG9ncmFtKClgIGZvciA8R0VPTV9GVU5DVElPTigpPi4gTGV04oCZcyBjcmVhdGUgYSBoaXN0b2dyYW0gb2YgbWVkaWFuIGhvdXNlaG9sZCBpbmNvbWUuIE5vdGUgdGhhdCB3ZSBkb27igJl0IG5lZWQgdG8gc3BlY2lmeSB0aGUgYHk9YCBoZXJlIGJlY2F1c2Ugd2UgYXJlIHBsb3R0aW5nIG9ubHkgb25lIHZhcmlhYmxlLiBXZSBwaXBlIGluIHRoZSBvYmplY3QgKmNhY291bnR5KiBpbnRvIGBnZ3Bsb3QoKWAgdG8gZXN0YWJsaXNoIHRoZSBiYXNlIGxheWVyLiBXZSB0aGVuIHVzZSBgZ2VvbV9oaXN0b2dyYW0oKWAgdG8gYWRkIHRoZSBkYXRhIGxheWVyIG9uIHRvcCBvZiB0aGUgYmFzZS4KCmBgYHtyfQpjYWNvdW50eSAlPiUKICBnZ3Bsb3QoKSArIAogIGdlb21faGlzdG9ncmFtKG1hcHBpbmcgPSBhZXMoeD1taGhpbmMpKSAKYGBgCgpXZSBjYW4gY29udGludWUgdG8gYWRkIGxheWVycyB0byB0aGUgcGxvdC4gRm9yIGV4YW1wbGUsIHdlIHVzZSB0aGUgYXJndW1lbnQgYHhsYWIoIk1lZGlhbiBob3VzZWhvbGQgaW5jb21lIilgIHRvIGxhYmVsIHRoZSB4LWF4aXMgYXMg4oCcTWVkaWFuIGhvdXNlaG9sZCBpbmNvbWXigJ0uCgpgYGB7cn0KY2Fjb3VudHkgJT4lCiAgZ2dwbG90KCkgKyAKICBnZW9tX2hpc3RvZ3JhbShtYXBwaW5nID0gYWVzKHg9bWhoaW5jKSkgKwogIHhsYWIoIk1lZGlhbiBob3VzZWhvbGQgaW5jb21lIikKYGBgCgpOb3RlIHRoZSBtZXNzYWdlIHByb2R1Y2VkIHdpdGggdGhlIHBsb3QuIEl0IHRlbGxzIHVzIHRoYXQgd2UgY2FuIHVzZSB0aGUgYGJpbnMgPWAgYXJndW1lbnQgdG8gY2hhbmdlIHRoZSBudW1iZXIgb2YgYmlucyB1c2VkIHRvIHByb2R1Y2UgdGhlIGhpc3RvZ3JhbS4gWW91IGNhbiBpbmNyZWFzZSB0aGUgbnVtYmVyIG9mIGJpbnMgdG8gbWFrZSB0aGUgYmlucyBuYXJyb3dlciBhbmQgdGh1cyBnZXQgYSBmaW5lciBncmFpbiBvZiBkZXRhaWwuIE9yIHlvdSBjYW4gZGVjcmVhc2UgdGhlIG51bWJlciBvZiBiaW5zIHRvIGdldCBhIGJyb2FkZXIgdmlzdWFsIHN1bW1hcnkgb2YgdGhlIHNoYXBlIG9mIHRoZSB2YXJpYWJsZeKAmXMgZGlzdHJpYnV0aW9uLiBDb21wYXJlIGJpbnMgPSAxMCB0byBiaW5zID0gNTAuCgpcCgpgYGB7cn0KY2Fjb3VudHkgJT4lCiAgZ2dwbG90KCkgKyAKICBnZW9tX2hpc3RvZ3JhbShtYXBwaW5nID0gYWVzKHg9bWhoaW5jKSwgYmlucz0xMCkgKwogIHhsYWIoIk1lZGlhbiBob3VzZWhvbGQgaW5jb21lIikKYGBgCgpcCgojIyMgQm94cGxvdAoKV2UgY2FuIHVzZSBhICpib3hwbG90KiB0byB2aXN1YWxseSBzdW1tYXJpemUgdGhlIGRpc3RyaWJ1dGlvbiBvZiBhIHNpbmdsZSBudW1lcmljIHZhcmlhYmxlIG9yIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBhIGNhdGVnb3JpY2FsIGFuZCBudW1lcmljIHZhcmlhYmxlLiBVc2UgYGdlb21fYm94cGxvdCgpYCBmb3IgPEdFT01fRlVOQ1RJT04oKT4gdG8gY3JlYXRlIGEgYm94cGxvdC4gTGV04oCZcyBleGFtaW5lIG1lZGlhbiBob3VzZWhvbGQgaW5jb21lLiBOb3RlIHRoYXQgYSBib3hwbG90IHVzZXMgeT0gcmF0aGVyIHRoYW4geD0gdG8gc3BlY2lmeSB3aGVyZSBtaGhpbmMgZ29lcy4gV2UgYWxzbyBwcm92aWRlIGEgZGVzY3JpcHRpdmUgeS1heGlzIGxhYmVsIHVzaW5nIHRoZSBgeWxhYigpYCBmdW5jdGlvbi4KCmBgYHtyfQpjYWNvdW50eSAlPiUKICBnZ3Bsb3QoKSArCiAgICBnZW9tX2JveHBsb3QobWFwcGluZyA9IGFlcyh5ID0gbWhoaW5jKSkgKwogICAgeWxhYigiTWVkaWFuIGhvdXNlaG9sZCBpbmNvbWUiKQpgYGAKClwKCkxldOKAmXMgZXhhbWluZSB0aGUgZGlzdHJpYnV0aW9uIG9mIG1lZGlhbiBpbmNvbWUgYnkgKm1oaXNwLiogQmVjYXVzZSB3ZSBhcmUgZXhhbWluaW5nIHRoZSBhc3NvY2lhdGlvbiBiZXR3ZWVuIHR3byB2YXJpYWJsZXMsIHdlIG5lZWQgdG8gc3BlY2lmeSB4IGFuZCB5IHZhcmlhYmxlcyBpbiBgYWVzKClgICh3ZSBhbHNvIHNwZWNpZnkgYm90aCB4LSBhbmQgeS1heGlzIGxhYmVscykuCgpgYGB7cn0KY2Fjb3VudHkgJT4lCiAgZ2dwbG90KCkgKwogICAgZ2VvbV9ib3hwbG90KG1hcHBpbmcgPSBhZXMoeCA9IG1oaXNwLCB5ID0gbWhoaW5jKSkgKwogICAgeGxhYigiTWFqb3JpdHkgSGlzcGFuaWMiKSArCiAgICB5bGFiKCJNZWRpYW4gaG91c2Vob2xkIGluY29tZSIpCmBgYAoKVGhlIHRvcCBhbmQgYm90dG9tIG9mIGEgYm94cGxvdCByZXByZXNlbnQgdGhlIDc1dGggYW5kIDI1dGggcGVyY2VudGlsZXMsIHJlc3BlY3RpdmVseS4gVGhlIGxpbmUgaW4gdGhlIG1pZGRsZSBvZiB0aGUgYm94IGlzIHRoZSA1MHRoIHBlcmNlbnRpbGUuIFBvaW50cyBvdXRzaWRlIHRoZSB3aGlza2VycyByZXByZXNlbnQgb3V0bGllcnMuIE91dGxpZXJzIGFyZSBkZWZpbmVkIGFzIGhhdmluZyB2YWx1ZXMgdGhhdCBhcmUgZWl0aGVyIGxhcmdlciB0aGFuIHRoZSA3NXRoIHBlcmNlbnRpbGUgcGx1cyAxLjUgdGltZXMgdGhlIElRUiBvciBzbWFsbGVyIHRoYW4gdGhlIDI1dGggcGVyY2VudGlsZSBtaW51cyAxLjUgdGltZXMgdGhlIElRUi4KClwKClRoZSBib3hwbG90IGlzIGZvciBhbGwgY291bnRpZXMgY29tYmluZWQuIFVzZSB0aGUgYGZhY2V0X3dyYXAoKWAgZnVuY3Rpb24gdG8gc2VwYXJhdGUgYnkgcmVnaW9uLiBOb3RpY2UgdGhlIHRpbGRlIH4gYmVmb3JlIHRoZSB2YXJpYWJsZSByZWdpb24gaW5zaWRlIGBmYWNldF93cmFwKClgLgoKYGBge3J9CmNhY291bnR5ICU+JQogIGdncGxvdCgpICsKICAgIGdlb21fYm94cGxvdChtYXBwaW5nID0gYWVzKHggPSBtaGlzcCwgeSA9IG1oaGluYykpICsKICAgIHhsYWIoIk1ham9yaXR5IEhpc3BhbmljIikgKwogICAgeWxhYigiTWVkaWFuIGhvdXNlaG9sZCBpbmNvbWUiKSArCiAgICBmYWNldF93cmFwKH5yZWdpb24pIApgYGAKClwKCiMjIyBCYXIgY2hhcnQKClRoZSBwcmltYXJ5IHB1cnBvc2Ugb2YgYSBiYXIgY2hhcnQgaXMgdG8gaWxsdXN0cmF0ZSBhbmQgY29tcGFyZSB0aGUgdmFsdWVzIGZvciBhIGNhdGVnb3JpY2FsIHZhcmlhYmxlLiBCYXIgY2hhcnRzIHNob3cgZWl0aGVyIHRoZSBudW1iZXIgb3IgZnJlcXVlbmN5IG9mIGVhY2ggY2F0ZWdvcnkuIFRvIGNyZWF0ZSBhIGJhciBjaGFydCwgdXNlIGBnZW9tX2JhcigpYCBmb3IgPEdFT01fRlVOQ1RJT04+KCkuIExldOKAmXMgc2hvdyBhIGJhciBjaGFydCBvZiBtZWRpYW4gaG91c2Vob2xkIGluY29tZSBieSByZWdpb24uIFdl4oCZbGwgYm9ycm93IGZyb20gY29kZSBhYm92ZSB0aGF0IGdlbmVyYXRlZCBhIHRpYmJsZSBvZiBtZWFuIGhvdXNlaG9sZCBpbmNvbWUgYnkgcmVnaW9uLCBhbmQgcGlwZSB0aGF0IGludG8gYGdncGxvdCgpYC4KCmBgYHtyfQpjYWNvdW50eSAlPiUKICBncm91cF9ieShyZWdpb24pICU+JQogIHN1bW1hcml6ZShNZWFuID0gbWVhbihtaGhpbmMpKSAlPiUKICBnZ3Bsb3QoYWVzKHg9cmVnaW9uLCB5ID0gTWVhbikpICsKICBnZW9tX2JhcihzdGF0ID0gIklkZW50aXR5IikgKwogIHhsYWIoIlJlZ2lvbiIpICsKICB5bGFiKCJBdmVyYWdlIGhvdXNlaG9sZCBpbmNvbWUiKQpgYGAKClwKClJpZ2h0IG5vdyB0aGUgYmFycyBhcmUgb3JkZXJlZCBiYXNlZCBvbiB0aGUgcmVnaW9uIG5hbWVzLiBXZSBjYW4gb3JkZXIgdGhlIGJhcnMgaW4gZGVzY2VuZGluZyBvcmRlciBiYXNlZCBvbiBob3VzZWhvbGQgaW5jb21lIGJ5IHVzaW5nIHRoZSBgcmVvcmRlcigpYCBmdW5jdGlvbi4gTm90aWNlIHRoZSBuZWdhdGl2ZSBzaWduIGluIGZyb250IG9mIE1lYW4gdG8gb3JkZXIgYnkgZGVzY2VuZGluZyBvcmRlci4KCmBgYHtyfQpjYWNvdW50eSAlPiUKICBncm91cF9ieShyZWdpb24pICU+JQogIHN1bW1hcml6ZShNZWFuID0gbWVhbihtaGhpbmMpKSAlPiUKICBnZ3Bsb3QoYWVzKHg9cmVvcmRlcihyZWdpb24sIC1NZWFuKSwgeSA9IE1lYW4pKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJJZGVudGl0eSIpICsKICB4bGFiKCJSZWdpb24iKSArCiAgeWxhYigiQXZlcmFnZSBob3VzZWhvbGQgaW5jb21lIikKYGBgCgpcCgpXZSBjYW4gZmxpcCB0aGUgYXhlcyB1c2luZyB0aGUgZnVuY3Rpb24gYGNvb3JkX2ZsaXAoKWAuCgpgYGB7cn0KY2Fjb3VudHkgJT4lCiAgZ3JvdXBfYnkocmVnaW9uKSAlPiUKICBzdW1tYXJpemUoTWVhbiA9IG1lYW4obWhoaW5jKSkgJT4lCiAgZ2dwbG90KGFlcyh4PXJlb3JkZXIocmVnaW9uLCAtTWVhbiksIHkgPSBNZWFuKSkgKwogIGdlb21fYmFyKHN0YXQgPSAiSWRlbnRpdHkiKSArCiAgeGxhYigiUmVnaW9uIikgKwogIHlsYWIoIkF2ZXJhZ2UgaG91c2Vob2xkIGluY29tZSIpICsKICBjb29yZF9mbGlwKCkKYGBgCgpcCgpXZWxsIGFyZW4ndCB3ZSBmYW5jeT8gYGdncGxvdCgpYCBpcyBhIHBvd2VyZnVsIGZ1bmN0aW9uLCBhbmQgeW91IGNhbiBtYWtlIGEgbG90IG9mIHZpc3VhbGx5IGNhcHRpdmF0aW5nIGdyYXBocy4gV2UgaGF2ZSBqdXN0IHNjcmF0Y2hlZCB0aGUgc3VyZmFjZSBvZiBpdHMgZnVuY3Rpb25zIGFuZCBmZWF0dXJlcy4gWW91IGNhbiBhbHNvIG1ha2UgeW91ciBncmFwaHMgcmVhbGx5IOKAnHByZXR0eeKAnSBhbmQgcHJvZmVzc2lvbmFsIGxvb2tpbmcgYnkgYWx0ZXJpbmcgZ3JhcGhpbmcgZmVhdHVyZXMsIGluY2x1ZGluZyBjb2xvcnMsIGxhYmVscywgdGl0bGVzIGFuZCBheGVzLiBGb3IgYSBsaXN0IG9mIGBnZ3Bsb3QoKWAgZnVuY3Rpb25zIHRoYXQgYWx0ZXIgdmFyaW91cyBmZWF0dXJlcyBvZiBhIGdyYXBoLCBjaGVjayBvdXQgQ2hhcHRlciAyOCBpbiBbUkRTXShodHRwczovL3I0ZHMuaGFkbGV5Lm56LykuCgpIZXJl4oCZcyB5b3VyICoqZ2dwbG90MioqIGJhZGdlLiBXZWFyIGl0IHdpdGggcHJpZGUhIE9LLCB3ZSd2ZSBkb25lIGEgbG90IHRvZGF5LiBHZXQgb3V0c2lkZSBhbmQgZ2V0IHNvbWUgZnJlc2ggYWlyIQoKXAoKIVtnZ3Bsb3QyIEJhZGdlXShnZ3Bsb3QyLnBuZyl7d2lkdGg9MjAlLCBoZWlnaHQ9MjAlfQoKClwKCiMgT3RoZXIgVVMgR292ZXJubWVudCBkYXRhc2V0cwoKQ2hlY2sgb3V0IHRoZSBbRGF0YSBTb3VyY2VzXShPdGhlcl8yMDI2Lmh0bWwpIGxpbmsgZm9yIG1vcmUgbGlua3MgdG8gVVMgR292ZXJubWVudCBEYXRhCgpcCgojIEFja25vd2xlZGdlbWVudHMKCk1ham9yIGFja25vd2xlZGdlbWVudHMgdG8gTm9saSBCcmF6aWwgKGFzIGFsd2F5cykgYW5kIFtDcmltZSBieSB0aGUgTnVtYmVyc10oaHR0cHM6Ly9jcmltZWJ5dGhlbnVtYmVycy5jb20vZ2VvY29kaW5nLmh0bWwpLiAKCgpMYWIgNC4KXAoKIyBNb3JlIHdpdGggdmVjdG9yIGRhdGEgYW5kIGludHJvZHVjaW5nIHJhc3RlcnMKCkluIHRoaXMgbGFiLCB3ZSBhcmUgZ29pbmcgdG8gd29yayBtb3JlIHdpdGggdmVjdG9yIGRhdGEuIFdlIHdpbGwgbGVhcm4gaG93IHRvIHZpc3VhbGl6ZSBDZW5zdXMgZGF0YSwgd2Ugd2lsbCB0YWxrIGFib3V0IGhvdyB0byB3cmFuZ2xlIHNwYXRpYWwgZGF0YSwgYW5kIHdlIHdpbGwgZ2V0IGludG8gc29tZSByZWFsbHkgY29vbCB3YXlzIHRvIGNyZWF0ZSBjaG9yb3BsZXRoIG1hcHMgKG1hcHMgY29sb3IgY29kZWQgYnkgYXR0cmlidXRlcykuIEZpbmFsbHksIHdlIHdpbGwgaW50cm9kdWNlIHRoZSBjb25jZXB0IG9mIHJhc3RlcnMuCgpUaGUgb2JqZWN0aXZlcyBvZiB0aGlzIGd1aWRlIGFyZSB0byB0ZWFjaCB5b3U6CgoxLiBWaXN1YWxpemUgVmVjdG9yIERhdGEKMi4gUHJvY2VzcyBWZWN0b3IgRGF0YQozLiBDcmVhdGUgUHVibGljYXRpb24tUmVhZHkgTWFwcwo0LiBJbnRyb2R1Y2UgUmFzdGVycwoKTGV0J3MgZ2V0IGNyYWNraW5nIQoKRmlyc3QsIGxldCdzIGluc3RhbGwgb3VyIHBhY2thZ2VzLgoKXApgYGB7cn0KbGlicmFyeShzZikKbGlicmFyeShNYXBHQU0pCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHRpZHljZW5zdXMpCmxpYnJhcnkoZmxleHRhYmxlKQpsaWJyYXJ5KFJDb2xvckJyZXdlcikKbGlicmFyeSh0bWFwKQpsaWJyYXJ5KHRlcnJhKQpgYGAKClwKCkluIFtMYWIgMl0oTGFiMl8yMDI2Lmh0bWwpLCB3ZSB3b3JrZWQgd2l0aCB0aGUgdGlkeWNlbnN1cyBwYWNrYWdlIGFuZCB0aGUgQ2Vuc3VzIEFQSSB0byBicmluZyBpbiBDZW5zdXMgZGF0YSBpbnRvIFIuIFdlIGNhbiB1c2UgdGhlIHNhbWUgY29tbWFuZHMgdG8gYnJpbmcgaW4gQ2Vuc3VzIGdlb2dyYXBoeS4gSWYgeW91IGhhdmVu4oCZdCBhbHJlYWR5LCBtYWtlIHN1cmUgdG8gW3NpZ24gdXAgZm9yIGFuZCBpbnN0YWxsIHlvdXIgQ2Vuc3VzIEFQSSBrZXldKGh0dHBzOi8vYXBpLmNlbnN1cy5nb3YvZGF0YS9rZXlfc2lnbnVwLmh0bWwpLiBJZiB5b3UgY291bGQgbm90IGluc3RhbGwgeW91ciBBUEkga2V5LCB5b3XigJlsbCBuZWVkIHRvIHVzZSBgY2Vuc3VzX2FwaV9rZXkoKWAgdG8gYWN0aXZhdGUgaXQgd2l0aCB0aGUgZm9sbG93aW5nIGNvZGU6CgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpjZW5zdXNfYXBpX2tleSgiWU9VUiBBUEkgS0VZIEdPRVMgSEVSRSIsIGluc3RhbGwgPSBUUlVFKQpgYGAKCmBgYHtyLCBlY2hvPUZBTFNFLCBldmFsPUZBTFNFfQpjZW5zdXNfYXBpX2tleSgiNWQ2ODkzNWM5NmMyNmVlNjdjYTUyZWI5NzNkNzFlNGE3Yjg0OTBhZCIsIGluc3RhbGwgPSBUUlVFLCBvdmVyd3JpdGU9VFJVRSkKYGBgClwKClVzZSB0aGUgYHNldF9hY3MoKWAgY29tbWFuZCB0byBicmluZyBpbiBDYWxpZm9ybmlhIHRyYWN0LWxldmVsIHJhY2UvZXRobmljaXR5IGNvdW50cywgdG90YWwgcG9wdWxhdGlvbiwgYW5kIHRvdGFsIG51bWJlciBvZiBob3VzZWhvbGRzLiBIb3cgZGlkIEkgZmluZCB0aGUgdmFyaWFibGUgSURzPyBDaGVjayBbTGFiIDJdKExhYjJfMjAyNi5odG1sKS4gU2luY2Ugd2Ugd2FudCB0cmFjdHMsIHdl4oCZbGwgdXNlIHRoZSBgZ2VvZ3JhcGh5ID0gInRyYWN0ImAgYXJndW1lbnQuCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQpjYS50cmFjdHMgPC0gZ2V0X2FjcyhnZW9ncmFwaHkgPSAidHJhY3QiLCAKICAgICAgICAgICAgICB5ZWFyID0gMjAyMywKICAgICAgICAgICAgICB2YXJpYWJsZXMgPSBjKHRwb3ByID0gIkIwMzAwMl8wMDEiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5od2hpdGUgPSAiQjAzMDAyXzAwMyIsIG5oYmxrID0gIkIwMzAwMl8wMDQiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5oYXNuID0gIkIwMzAwMl8wMDYiLCBoaXNwID0gIkIwMzAwMl8wMTIiKSwgCiAgICAgICAgICAgICAgc3RhdGUgPSAiQ0EiLAogICAgICAgICAgICAgIG91dHB1dCA9ICJ3aWRlIiwKICAgICAgICAgICAgICBzdXJ2ZXkgPSAiYWNzNSIsCiAgICAgICAgICAgICAgZ2VvbWV0cnkgPSBUUlVFLAogICAgICAgICAgICAgIGNiID0gRkFMU0UpCmBgYAoKXAoKVGhlIG9ubHkgZGlmZmVyZW5jZSBiZXR3ZWVuIHRoZSBjb2RlIGFib3ZlIGFuZCB3aGF0IHdlIHVzZWQgaW4gW0xhYiAyXShMYWIyXzIwMjYuaHRtbCkgaXMgd2UgaGF2ZSBvbmUgYWRkaXRpb25hbCBhcmd1bWVudCBhZGRlZCB0byB0aGUgYGdldF9hY3MoKWAgY29tbWFuZDogYGdlb21ldHJ5ID0gVFJVRWAuIFRoaXMgdGVsbHMgUiB0byBicmluZyBpbiB0aGUgc3BhdGlhbCBmZWF0dXJlcyBhc3NvY2lhdGVkIHdpdGggdGhlIGdlb2dyYXBoeSB5b3Ugc3BlY2lmaWVkIGluIHRoZSBjb21tYW5kLCBpbiB0aGUgYWJvdmUgY2FzZSBDYWxpZm9ybmlhIHRyYWN0cy4gWW91IGNhbiBzZXQgYGNhY2hlX3RhYmxlID0gVFJVRWAgc28gdGhhdCB5b3UgZG9u4oCZdCBoYXZlIHRvIHJlLWRvd25sb2FkIGFmdGVyIHlvdeKAmXZlIGRvd25sb2FkZWQgc3VjY2Vzc2Z1bGx5IHRoZSBmaXJzdCB0aW1lLiBUaGlzIGlzIGltcG9ydGFudCBiZWNhdXNlIHlvdSBtaWdodCBiZSBkb3dubG9hZGluZyBhIHJlYWxseSBsYXJnZSBmaWxlLCBvciBtYXkgZW5jb3VudGVyIENlbnN1cyBGVFAgaXNzdWVzIHdoZW4gdHJ5aW5nIHRvIGNvbGxlY3QgZGF0YS4gCgpcCgpOb3RlOiBXZSBjYW4gYWxzbyBkb3dubG9hZCB0aGUgZGF0YSBhbm90aGVyIHdheS4gV2UgY2FuIGdvIHRvIHRoZSBbQ2Vuc3VzIFNoYXBlZmlsZXMgd2Vic2l0ZV0oaHR0cHM6Ly93d3cuY2Vuc3VzLmdvdi9jZ2ktYmluL2dlby9zaGFwZWZpbGVzL2luZGV4LnBocCkgYW5kIG5hdmlnYXRlIHRvIDIwMjMsIENlbnN1cyBUcmFjdHMsIHRoZW4gQ2FsaWZvcm5pYS4gV2UgY2FuIHRoZW4gZG93bmxvYWQgYSAuemlwIGZpbGUgdGhhdCBjb250YWlucyBhbiBFU1JJIHNoYXBlZmlsZSBvZiB0aGUgQ2Vuc3VzIHRyYWN0cyBmb3IgQ2FsaWZvcm5pYS4gV2hlbiB3ZSB1bnppcCB0aGUgZmlsZSwgd2Ugc2VlIGEgc2VyaWVzIG9mIGZpbGVzLiBUaGFua2Z1bGx5LCB0aGUgKipzZioqIHBhY2thZ2UgaGFzIGFuIGBzdF9yZWFkKClgIGZ1bmN0aW9uIHRoYXQgY2FuIHRhY2tsZSB0aGlzISBGb3IgbW9yZSBkZXRhaWxlZCBkYXRhIGRvd25sb2FkcywgeW91IGNhbiB1c2UgW05hdGlvbmFsIEhpc3RvcmljYWwgR2VvZ3JhcGhpYyBJbmZvcm1hdGlvbiBTeXN0ZW0gKE5IR0lTKV0oaHR0cHM6Ly93d3cubmhnaXMub3JnLykuIFRoZSBjb2RlIGJlbG93IGlzIGV4YW1wbGUgb2YgaG93IHdlIG1pZ2h0IGJyaW5nIGluIGEgc2hhcGVmaWxlLCBqdXN0IGZvciBmdXR1cmUgcmVmZXJlbmNlIQoKYGBge3IgZXZhbD1GQUxTRX0KY2EudHJhY3RzIDwtIHN0X3JlYWQoIi9Vc2Vycy9wamFtZXMxL0Rvd25sb2Fkcy90bF8yMDI0XzA2X3RyYWN0L3RsXzIwMjRfMDZfdHJhY3Quc2hwIikKYGBgCgoKT0ssIGxldCdzIGdvIGJhY2sgdG8gdGhlIGRhdGEgd2UgZ290IGZyb20gKip0aWR5Y2Vuc3VzKiouIExldHMgdGFrZSBhIGxvb2sgYXQgb3VyIGRhdGEuCgpcCgpgYGB7cn0KY2EudHJhY3RzCmBgYAoKXAoKVGhlIG9iamVjdCBsb29rcyBtdWNoIGxpa2UgYSBiYXNpYyB0aWJibGUsIGJ1dCB3aXRoIGEgZmV3IGRpZmZlcmVuY2VzLgoKICAtIFlvdeKAmWxsIGZpbmQgdGhhdCB0aGUgZGVzY3JpcHRpb24gb2YgdGhlIG9iamVjdCBub3cgaW5kaWNhdGVzIHRoYXQgaXQgaXMgYSBzaW1wbGUgZmVhdHVyZSBjb2xsZWN0aW9uIHdpdGggOSwxMjkgZmVhdHVyZXMgKHRyYWN0cyBpbiBDYWxpZm9ybmlhKSB3aXRoIDEzIGZpZWxkcyAoYXR0cmlidXRlcyBvciBjb2x1bW5zIG9mIGRhdGEpLgogIC0gVGhlIGBHZW9tZXRyeSBUeXBlYCBpbmRpY2F0ZXMgdGhhdCB0aGUgc3BhdGlhbCBkYXRhIGFyZSBpbiBgTVVMVElQT0xZR09OYCBmb3JtIChhcyBvcHBvc2VkIHRvIHBvaW50cyBvciBsaW5lcywgdGhlIG90aGVyIGJhc2ljIHZlY3RvciBkYXRhIGZvcm1zKS4KICAtIGBCb3VuZGluZyBib3hgIGluZGljYXRlcyB0aGUgc3BhdGlhbCBleHRlbnQgb2YgdGhlIGZlYXR1cmVzIChmcm9tIGxlZnQgdG8gcmlnaHQsIGZvciBleGFtcGxlLCBDYWxpZm9ybmlhIHRyYWN0cyBnbyBmcm9tIGEgbG9uZ2l0dWRlIG9mIC0xMjQuNDgyIHRvIC0xMTQuMTMxMikuCiAgLSBgR2VvZGV0aWMgQ1JTYCB0ZWxscyB1cyB0aGUgY29vcmRpbmF0ZSByZWZlcmVuY2Ugc3lzdGVtLgogIC0gVGhlIGZpbmFsIGRpZmZlcmVuY2UgaXMgdGhhdCB0aGUgZGF0YSBmcmFtZSBjb250YWlucyB0aGUgY29sdW1uIGdlb21ldHJ5LiBUaGlzIGNvbHVtbiAoYSBsaXN0LWNvbHVtbikgY29udGFpbnMgdGhlIGdlb21ldHJ5IGZvciBlYWNoIG9ic2VydmF0aW9uLiBUaGlzIGxvb2tzIGZhbWlsaWFyIQogIApBdCBpdHMgbW9zdCBiYXNpYywgYW4gKipzZioqIG9iamVjdCBpcyBhIGNvbGxlY3Rpb24gb2Ygc2ltcGxlIGZlYXR1cmVzIHRoYXQgaW5jbHVkZXMgYXR0cmlidXRlcyBhbmQgZ2VvbWV0cmllcyBpbiB0aGUgZm9ybSBvZiBhIGRhdGEgZnJhbWUuIEluIG90aGVyIHdvcmRzLCBpdCBpcyBhIGRhdGEgZnJhbWUgKG9yIHRpYmJsZSkgd2l0aCByb3dzIG9mIGZlYXR1cmVzLCBjb2x1bW5zIG9mIGF0dHJpYnV0ZXMsIGFuZCBhIHNwZWNpYWwgY29sdW1uIGFsd2F5cyBuYW1lZCBnZW9tZXRyeSB0aGF0IGNvbnRhaW5zIHRoZSBzcGF0aWFsIGFzcGVjdHMgb2YgdGhlIGZlYXR1cmVzLgoKSWYgeW91IHdhbnQgdG8gcGVlayBiZWhpbmQgdGhlIGN1cnRhaW4gYW5kIGxlYXJuIG1vcmUgYWJvdXQgdGhlIG5pdHR5IGdyaXR0eSBkZXRhaWxzIGFib3V0IHNpbXBsZSBmZWF0dXJlcywgY2hlY2sgb3V0IHRoZSBvZmZpY2lhbCAqKnNmKiogW3ZpZ25ldHRlLl0oaHR0cHM6Ly9yLXNwYXRpYWwuZ2l0aHViLmlvL3NmL2FydGljbGVzL3NmMS5odG1sKQoKXAoKIyBEYXRhIFdyYW5nbGluZwoKVGhlcmUgaXMgYSBsb3Qgb2Ygc3R1ZmYgW2JlaGluZCB0aGUgY3VydGFpbl0oaHR0cHM6Ly93d3cuamVzc2VzYWRsZXIuY29tL3Bvc3Qvc2ltcGxlLWZlYXR1cmUtb2JqZWN0cy8pIG9mIGhvdyBSIGhhbmRsZXMgc3BhdGlhbCBkYXRhIGFzIHNpbXBsZSBmZWF0dXJlcywgYnV0IHRoZSBtYWluIHRha2Vhd2F5IGlzIHRoYXQgKipzZioqIG9iamVjdHMgYXJlIGRhdGEgZnJhbWVzLiBUaGlzIG1lYW5zIHlvdSBjYW4gdXNlIG1hbnkgb2YgdGhlICoqdGlkeXZlcnNlKiogZnVuY3Rpb25zIHdl4oCZdmUgbGVhcm5lZCBpbiB0aGUgcGFzdCBjb3VwbGUgbGFicyB0byBtYW5pcHVsYXRlICoqc2YqKiBvYmplY3RzLCBpbmNsdWRpbmcgdGhlIHBpcGUgYCU+JWAgb3BlcmF0b3IuIEZvciBleGFtcGxlLCBsZXTigJlzIGJyZWFrIHVwIHRoZSBjb2x1bW4gKk5BTUUqIGludG8gc2VwYXJhdGUgdHJhY3QsIGNvdW50eSBhbmQgc3RhdGUgdmFyaWFibGVzIHVzaW5nIHRoZSBgc2VwYXJhdGUoKWAgZnVuY3Rpb24KCldlIGRvIGFsbCBvZiB0aGlzIGluIG9uZSBsaW5lIG9mIGNvbnRpbnVvdXMgY29kZSB1c2luZyB0aGUgcGlwZSBvcGVyYXRvciBgJT4lYAoKYGBge3J9CmNhLnRyYWN0cyA8LSBjYS50cmFjdHMgJT4lCiAgICAgICAgICAgICAgc2VwYXJhdGUoTkFNRSwgYygiVHJhY3QiLCAiQ291bnR5IiwgIlN0YXRlIiksIHNlcCA9ICI7ICIpCmdsaW1wc2UoY2EudHJhY3RzKQpgYGAKClwKCkFub3RoZXIgaW1wb3J0YW50IGRhdGEgd3JhbmdsaW5nIG9wZXJhdGlvbiBpcyB0byBqb2luIGF0dHJpYnV0ZSBkYXRhIHRvIGFuIHNmIG9iamVjdC4gRm9yIGV4YW1wbGUsIGxldOKAmXMgc2F5IHlvdSB3YW50ZWQgdG8gYWRkIHRyYWN0IGxldmVsIG1lZGlhbiBob3VzZWhvbGQgaW5jb21lLCB3aGljaCBpcyBsb2NhdGVkIGluIHRoZSBmaWxlIGNhX21lZF9pbmNfMjAxOC5jc3YuIFJlYWQgdGhlIGZpbGUgaW4uCgpgYGB7cn0KY2EuaW5jIDwtIGdldF9hY3MoZ2VvZ3JhcGh5ID0gInRyYWN0IiwgCiAgICAgICAgICAgICAgeWVhciA9IDIwMjMsCiAgICAgICAgICAgICAgdmFyaWFibGVzID0gYyhtZWRpbmMgPSAiQjE5MDEzXzAwMSIpLCAKICAgICAgICAgICAgICBzdGF0ZSA9ICJDQSIsCiAgICAgICAgICAgICAgc3VydmV5ID0gImFjczUiLAogICAgICAgICAgICAgIG91dHB1dCA9ICJ3aWRlIikKYGBgCgpcCgpVbmxpa2UgYmVmb3JlLCB3ZSBicm91Z2h0IHRoZXNlIGRhdGEgaW4gd2l0aG91dCB0aGUgYGdlb21ldHJ5ID0gVFJVRWAgb3B0aW9uLiBTbyB0aGlzIGlzIGp1c3QgYSB0YWJsZS4gQnV0IHJlbWVtYmVyLCBhbiAqKnNmKiogb2JqZWN0IGlzIGEgZGF0YSBmcmFtZSwgc28gd2UgY2FuIHVzZSBgbGVmdF9qb2luKClgLCB3aGljaCB3ZSBjb3ZlcmVkIGluIFtMYWIgMV0oTGFiMV8yMDI2Lmh0bWwpLCB0byBqb2luIHRoZSBmaWxlcyAqY2EuaW5jKiBhbmQgKmNhLnRyYWN0cyouCgpgYGB7cn0KY2EudHJhY3RzIDwtIGNhLnRyYWN0cyAlPiUKICBsZWZ0X2pvaW4oY2EuaW5jLCBieSA9ICJHRU9JRCIpCgojdGFrZSBhIGxvb2sgdG8gbWFrZSBzdXJlIHRoZSBqb2luIHdvcmtlZApnbGltcHNlKGNhLnRyYWN0cykKYGBgCgpcCgpOb3RlIHRoYXQgd2UgY2Fu4oCZdCB1c2UgYGxlZnRfam9pbigpYCB0byBqb2luIHRoZSBhdHRyaWJ1dGUgdGFibGVzIG9mIHR3byAqKnNmKiogZmlsZXMuIFlvdSB3aWxsIG5lZWQgdG8gZWl0aGVyIG1ha2Ugb25lIG9mIHRoZW0gbm90IHNwYXRpYWwgYnkgdXNpbmcgdGhlIGBzdF9kcm9wX2dlb21ldHJ5KClgIGZ1bmN0aW9uIG9yIHVzZSB0aGUgYHN0X2pvaW4oKWAgZnVuY3Rpb24gdG8gc3BhdGlhbGx5IGpvaW4gdGhlbS4KCldlIHVzZSB0aGUgZnVuY3Rpb24gYHRtX3NoYXBlKClgIGZyb20gdGhlICoqdG1hcCoqIHBhY2thZ2UgdG8gbWFwIHRoZSBkYXRhLiAKCmBgYHtyfQp0bWFwX21vZGUoInBsb3QiKQp0cmFjdF9tYXAgPC0gdG1fc2hhcGUoY2EudHJhY3RzKSArICAgdG1fcG9seWdvbnMoKQp0cmFjdF9tYXAKYGBgCgpcCgojIFNwYXRpYWwgRGF0YSBXcmFuZ2xpbmcKClRoZXJlIGlzIERhdGEgV3JhbmdsaW5nIGFuZCB0aGVuIHRoZXJlIGlzIFNwYXRpYWwgRGF0YSBXcmFuZ2xpbmcuIEN1ZSBkYW5nZXJvdXMgc291bmRpbmcgbXVzaWMuIFdlbGwsIGl04oCZcyBub3QgdGhhdCBkYW5nZXJvdXMgb3Igc2NhcnkuIFNwYXRpYWwgRGF0YSBXcmFuZ2xpbmcgaW52b2x2ZXMgY2xlYW5pbmcgb3IgYWx0ZXJpbmcgeW91ciBkYXRhIHNldCBiYXNlZCBvbiB0aGUgZ2VvZ3JhcGhpYyBsb2NhdGlvbiBvZiBmZWF0dXJlcy4gVGhlICoqc2YqKiBwYWNrYWdlIG9mZmVycyBhIHN1aXRlIG9mIGZ1bmN0aW9ucyB1bmlxdWUgdG8gd3JhbmdsaW5nIHNwYXRpYWwgZGF0YS4gTW9zdCBvZiB0aGVzZSBmdW5jdGlvbnMgc3RhcnQgb3V0IHdpdGggdGhlIHByZWZpeCBgc3RfYC4gVG8gc2VlIGFsbCBvZiB0aGUgZnVuY3Rpb25zLCB0eXBlIGluCgpgYGB7ciwgZXZhbD1GQUxTRX0KbWV0aG9kcyhjbGFzcyA9ICJzZiIpCmBgYAoKXAoKV2Ugd29u4oCZdCBnbyB0aHJvdWdoIGFsbCBvZiB0aGVzZSBmdW5jdGlvbnMgYXMgdGhlIGxpc3QgaXMgcXVpdGUgZXh0ZW5zaXZlLiBCdXQsIHdl4oCZbGwgZ28gdGhyb3VnaCBhIGZldyByZWxldmFudCBzcGF0aWFsIG9wZXJhdGlvbnMgZm9yIHRoaXMgY2xhc3MgYmVsb3cuIFRoZSBmdW5jdGlvbiB3ZSB3aWxsIGJlIHByaW1hcmlseSB1c2luZyBpcyBgc3Rfam9pbigpYC4KClwKCiMjIEludGVyc2VjdAoKQSBjb21tb24gc3BhdGlhbCBkYXRhIHdyYW5nbGluZyBpc3N1ZSBpcyB0byBzdWJzZXQgYSBzZXQgb2Ygc3BhdGlhbCBvYmplY3RzIGJhc2VkIG9uIHRoZWlyIGxvY2F0aW9uIHJlbGF0aXZlIHRvIGFub3RoZXIgc3BhdGlhbCBvYmplY3QuIEluIG91ciBjYXNlLCB3ZSB3YW50IHRvIGtlZXAgQ2FsaWZvcm5pYSB0cmFjdHMgdGhhdCBhcmUgaW4gdGhlIFNhY3JhbWVudG8gbWV0cm8gYXJlYS4gV2UgY2FuIGRvIHRoaXMgdXNpbmcgdGhlIGBzdF9qb2luKClgIGZ1bmN0aW9uLiBXZeKAmWxsIG5lZWQgdG8gc3BlY2lmeSBhIHR5cGUgb2Ygam9pbi4gTGV04oCZcyBmaXJzdCB0cnkgYGpvaW4gPSBzdF9pbnRlcnNlY3RzYC4gRmlyc3QsIGxldCdzIGJyaW5nIGluIGEgcG9seWdvbiBvZiB0aGUgU2FjcmFtZW50byBtZXRybyBhcmVhIGZyb20gR2l0aHViLgoKYGBge3J9CnVybCA8LSAiaHR0cHM6Ly9naXRodWIuY29tL3BqYW1lcy11Y2RhdmlzL1NQSDIxNS9yYXcvbWFpbi9zYWMubWV0cm8ucmRzIgpkb3dubG9hZC5maWxlKHVybCwgZGVzdGZpbGUgPSAic2FjLm1ldHJvLnJkcyIsIG1vZGUgPSAid2IiKQpzYWMubWV0cm8gPC0gcmVhZFJEUygic2FjLm1ldHJvLnJkcyIpCmBgYAoKXAoKTGV0J3MgdGFrZSBhIGxvb2sgYXQgKnNhYy5tZXRybyogYW5kIHVuZGVyc3RhbmQgd2hhdCBmaWxlIGl0IGlzLgoKYGBge3J9CmdsaW1wc2Uoc2FjLm1ldHJvKQpgYGAKClwKCk9LLCB0aGF0IGdlb21ldHJ5IGxvb2tzIGdvb2QuIEFuZCBpdCdzIGEgcG9seWdvbiwgc28gdGhhdCdzIGdvb2QuIExldCdzIG5vdyB0cnkgdG8gaW50ZXJzZWN0IG91ciAqc2FjLm1ldHJvKiBkYXRhc2V0IHdpdGggb3VyICpjYS50cmFjdHMqIGRhdGFzZXQuIAoKYGBge3J9CnNhYy5tZXRyby50cmFjdHMuaW50IDwtIHN0X2pvaW4oY2EudHJhY3RzLCBzYWMubWV0cm8sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGpvaW4gPSBzdF9pbnRlcnNlY3RzLCBsZWZ0PUZBTFNFKQpgYGAKClwKClRoZSBhYm92ZSBjb2RlIHRlbGxzIFIgdG8gaWRlbnRpZnkgdGhlIHBvbHlnb25zIGluICpjYS50cmFjdHMqIHRoYXQgaW50ZXJzZWN0IHdpdGggdGhlIHBvbHlnb24gKnNhYy5tZXRybyouIFdlIGluZGljYXRlIHdlIHdhbnQgYSBwb2x5Z29uIGludGVyc2VjdGlvbiBieSBzcGVjaWZ5aW5nIGBqb2luID0gc3RfaW50ZXJzZWN0c2AuIFRoZSBvcHRpb24gYGxlZnQ9RkFMU0VgIHRlbGxzIFIgdG8gcmVtb3ZlIHRoZSBwb2x5Z29ucyBmcm9tICpjYS50cmFjdHMqIHRoYXQgZG8gbm90IGludGVyc2VjdCAobWFrZSBpdCBUUlVFIGFuZCBzZWUgd2hhdCBoYXBwZW5zKSB3aXRoICpzYWMubWV0cm8qLiBQbG90dGluZyBvdXIgdHJhY3RzLCB3ZSBnZXQ6CmBgYHtyfQp0bV9zaGFwZShzYWMubWV0cm8udHJhY3RzLmludCkgKwogIHRtX3BvbHlnb25zKGNvbCA9ICJibHVlIikgKwp0bV9zaGFwZShzYWMubWV0cm8pICsgIAogIHRtX2JvcmRlcnMoY29sID0gInJlZCIpCmBgYAoKXAoKIyMgV2l0aGluCgpXZSBoYXZlIG9uZSBzbWFsbCBpc3N1ZS4gVXNpbmcgYGpvaW4gPSBzdF9pbnRlcnNlY3RzYCByZXR1cm5zIGFsbCB0cmFjdHMgdGhhdCBpbnRlcnNlY3QgKnNhYy5tZXRybyosIHdoaWNoIGluY2x1ZGUgdGhvc2UgdGhhdCB0b3VjaCB0aGUgbWV0cm/igJlzIGJvdW5kYXJ5LiBObyBidWVuby4gV2UgY2FuIGluc3RlYWQgdXNlIHRoZSBhcmd1bWVudCBgam9pbiA9IHN0X3dpdGhpbmAgdG8gcmV0dXJuIHRyYWN0cyB0aGF0IGFyZSAqY29tcGxldGVseSB3aXRoaW4qIHRoZSBtZXRybyBhcmVhLgoKYGBge3J9CnNhYy5tZXRyby50cmFjdHMudyA8LSBzdF9qb2luKGNhLnRyYWN0cywgc2FjLm1ldHJvLCBqb2luID0gc3Rfd2l0aGluLCBsZWZ0PUZBTFNFKQoKdG1fc2hhcGUoc2FjLm1ldHJvLnRyYWN0cy53KSArCiAgdG1fcG9seWdvbnMoY29sID0gImJsdWUiKSArCnRtX3NoYXBlKHNhYy5tZXRybykgKwogIHRtX2JvcmRlcnMoY29sID0gInJlZCIpCmBgYAoKXAoKTG9va2luZyBtdWNoIGJldHRlciEgTm93LCBpZiB3ZSBsb29rIGF0ICpzYWMubWV0cm8udHJhY3RzLncq4oCZcyBhdHRyaWJ1dGUgdGFibGUsIHlvdeKAmWxsIHNlZSBpdCBpbmNsdWRlcyBhbGwgdGhlIHZhcmlhYmxlcyBmcm9tIGJvdGggKmNhLnRyYWN0cyogYW5kICpzYWMubWV0cm8qLiBXZSBkb27igJl0IG5lZWQgdGhlc2UgdmFyaWFibGVzLCBzbyB1c2UgYHNlbGVjdCgpYCB0byBlbGltaW5hdGUgdGhlbS4gWW914oCZbGwgYWxzbyBub3RpY2UgdGhhdCBpZiB2YXJpYWJsZXMgZnJvbSB0d28gZGF0YSBzZXRzIHNoYXJlIHRoZSBzYW1lIG5hbWUsIFIgd2lsbCBrZWVwIGJvdGggYW5kIGF0dGFjaCBhICoueCogYW5kICoueSogdG8gdGhlIGVuZC4gRm9yIGV4YW1wbGUsIEkgd2FzIGZvdW5kIGluIGJvdGggKmNhLnRyYWN0cyogYW5kICpzYWMubWV0cm8qLCBzbyBSIG5hbWVkIG9uZSAqR0VPSUQueCogYW5kIHRoZSBvdGhlciB0aGF0IHdhcyBtZXJnZWQgaW4gd2FzIG5hbWVkICpHRU9JRC55Ki4KClwKCiMgTWFwcGluZyBpbiBSCgojIyBnZ3Bsb3QKCk9LLCBzbyBub3cgd2UndmUgdGFsa2VkIGEgbGl0dGxlIGFib3V0IGhvdyB0byBicmluZyBpbiBhbmQgbWFuaXB1bGF0ZSB2ZWN0b3IgcG9seWdvbiBkYXRhLCBsZXQncyBkbyBzb21lIG1hcHBpbmcgYW5kIGNyZWF0ZSBzb21lIGNob3JvcGxldGggbWFwcy4gV2UgY2FuIGRvIHRoaXMgd2l0aCB0aGUgKipnZ3Bsb3QqKiBwYWNrYWdlLCB0aGUgKip0bWFwKiogcGFja2FnZSwgYW5kIHRoZSAqKmxlYWZsZXQqKiBwYWNrYWdlICh3aGljaCB3ZSB3b24ndCBjb3ZlciBub3csIGJ1dCBpdCdzIHZlcnkgY29vbCBmb3IgaW50ZXJhY3RpdmUgbWFwcykuIExldCdzIHN0YXJ0IHdpdGggKipnZ3Bsb3QqKi4KClwKCiMjIyBnZ3Bsb3QKCkJlY2F1c2UgKipzZioqIGlzIHRpZHkgZnJpZW5kbHksIGl0IGlzIG5vIHN1cnByaXNlIHdlIGNhbiB1c2UgdGhlICoqdGlkeXZlcnNlKiogcGxvdHRpbmcgZnVuY3Rpb24gYGdncGxvdCgpYCB0byBtYWtlIG1hcHMuIFdlIGFscmVhZHkgcmVjZWl2ZWQgYW4gaW50cm9kdWN0aW9uIHRvIGBnZ3Bsb3QoKWAgaW4gW0xhYiAyXShMYWIyXzIwMjYuaHRtbCkuIFJlY2FsbCBpdHMgYmFzaWMgc3RydWN0dXJlOgoKYGBge3IsIGV2YWw9RkFMU0V9CmdncGxvdChkYXRhID0gPERBVEE+KSArCiAgICAgIDxHRU9NX0ZVTkNUSU9OPihtYXBwaW5nID0gYWVzKHgsIHkpKSArCiAgICAgIDxPUFRJT05TPigpCmBgYAoKXAoKSW4gbWFwcGluZywgYGdlb21fc2YoKWAgaXMgYDxHRU9NX0ZVTkNUSU9OPigpYC4gVW5saWtlIHdpdGggZnVuY3Rpb25zIGxpa2UgYGdlb21faGlzdG9ncmFtKClgIGFuZCBgZ2VvbV9ib3hwbG90KClgLCB3ZSBkb27igJl0IHNwZWNpZnkgYW4geCBhbmQgeSBheGlzLiBJbnN0ZWFkIHlvdSB1c2UgYGZpbGxgIGlmIHlvdSB3YW50IHRvIG1hcCBhIHZhcmlhYmxlIG9yIGNvbG9yIHRvIGp1c3QgbWFwIGJvdW5kYXJpZXMuCgpMZXTigJlzIHVzZSBgZ2dwbG90KClgIHRvIG1ha2UgYSBjaG9yb3BsZXRoIG1hcC4gV2UgbmVlZCB0byBzcGVjaWZ5IGEgbnVtZXJpYyB2YXJpYWJsZSBpbiB0aGUgYGZpbGwgPWAgYXJndW1lbnQgd2l0aGluIGBnZW9tX3NmKClgLiBIZXJlIHdlIG1hcCB0cmFjdC1sZXZlbCBtZWRpYW4gaG91c2Vob2xkIGluY29tZSBpbiB0aGUgU2FjcmFtZW50byBtZXRybyBhcmVhLgoKYGBge3J9CmdncGxvdChkYXRhID0gc2FjLm1ldHJvLnRyYWN0cy53KSArCiAgZ2VvbV9zZihhZXMoZmlsbCA9IG1lZGluY0UpKQpgYGAKClwKCldlIGNhbiBhbHNvIHNwZWNpZnkgYSB0aXRsZSAoYXMgd2VsbCBhcyBzdWJ0aXRsZXMgYW5kIGNhcHRpb25zKSB1c2luZyB0aGUgYGxhYnMoKWAgZnVuY3Rpb24uCgoKYGBge3J9CmdncGxvdChkYXRhID0gc2FjLm1ldHJvLnRyYWN0cy53KSArCiAgZ2VvbV9zZihhZXMoZmlsbCA9IG1lZGluY0UpKSArCiAgICBsYWJzKHRpdGxlID0gIk1lZGlhbiBJbmNvbWUgU2FjcmFtZW50byBNU0EgVHJhY3RzIikgCmBgYAoKXAoKV2UgY2FuIG1ha2UgZnVydGhlciBsYXlvdXQgYWRqdXN0bWVudHMgdG8gdGhlIG1hcC4gRG9u4oCZdCBsaWtlIGEgYmx1ZSBzY2FsZSBvbiB0aGUgbGVnZW5kPyBZb3UgY2FuIGNoYW5nZSBpdCB1c2luZyB0aGUgYHNjYWxlX2ZpbGVfZ3JhZGllbnQoKWAgZnVuY3Rpb24uIExldOKAmXMgY2hhbmdlIGl0IHRvIGEgd2hpdGUgdG8gcmVkIGdyYWRpZW50LiBXZSBjYW4gYWxzbyBlbGltaW5hdGUgdGhlIGdyYXkgdHJhY3QgYm9yZGVyIGNvbG9ycyB0byBtYWtlIHRoZSBmaWxsIGNvbG9yIGRpc3RpbmN0aW9uIGNsZWFyZXIuIFdlIGRvIHRoaXMgYnkgc3BlY2lmeWluZyBgY29sb3IgPSBOQWAgaW5zaWRlIGBnZW9tX3NmKClgLiBXZSBjYW4gYWxzbyBnZXQgcmlkIG9mIHRoZSBncmF5IGJhY2tncm91bmQgYnkgc3BlY2lmeWluZyBhIGJhc2ljIGJsYWNrIGFuZCB3aGl0ZSB0aGVtZSB1c2luZyBgdGhlbWVfYncoKWAuIFdlIGFsc28gYWRkZWQgYSBjYXB0aW9uIGluZGljYXRpbmcgdGhlIHNvdXJjZSBvZiB0aGUgZGF0YSB1c2luZyB0aGUgYGNhcHRpb25zID1gIHBhcmFtZXRlciB3aXRoaW4gYGxhYnMoKWAuIFdlIHRoZW4gY2hhbmdlZCB0aGUgY29sb3IgdG8gcmVkIHVzaW5nIGxhYmVscyBmb3IgYGxvdz1gIGFuZCBgaGlnaD1gLCBhbmQgd2UgYWRkZWQgYSBuYW1lIHRvIG91ciBsZWdlbmQgd2l0aCBgbmFtZT0nLgoKYGBge3J9CmdncGxvdChkYXRhID0gc2FjLm1ldHJvLnRyYWN0cy53KSArCiAgZ2VvbV9zZihhZXMoZmlsbCA9IG1lZGluY0UpLCBjb2xvciA9IE5BKSArCiAgICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdz0gIndoaXRlIiwgaGlnaCA9ICJyZWQiLCBuYS52YWx1ZSA9ImdyYXkiLCBuYW1lID0gIk1lZGlhbiBJbmNvbWUiKSArICAKICAgIGxhYnModGl0bGUgPSAiTWVkaWFuIEluY29tZSBTYWNyYW1lbnRvIE1TQSBUcmFjdHMiLAogICAgICAgICBjYXB0aW9uID0gIlNvdXJjZTogQW1lcmljYW4gQ29tbXVuaXR5IFN1cnZleSIpICsgIAogIHRoZW1lX2J3KCkKYGBgCgpcCgpEYXJlIEkgc2F5LCB3ZSBhcmUgcmVhZHkgZm9yIHRoZSBOZXcgWW9yayBUaW1lcyB3aXRoIHRoaXMgbWFwIQoKXAoKIyMjIFBvaW50cyBvbiB0b3Agb2YgcG9seWdvbnMKCk9LLCBub3cgdGhhdCB3ZSBoYXZlIG1hcHBlZCBwb2ludHMgYW5kIG1hcHBlZCBwb2x5Z29ucywgbGV0J3MgcHV0IHRoZW0gYm90aCB0b2dldGhlciEgRmlyc3QsIHdlIGFyZSBnb2luZyB0byBicmluZyBpbiBvdXIgb2xkIGZyaWVuZCAqQ0FkYXRhKiBmcm9tIGxhc3Qgd2VlaydzIGxhYi4KCmBgYHtyfQpkYXRhKENBZGF0YSkKY2FfcHRzIDwtIENBZGF0YQpzdW1tYXJ5KGNhX3B0cykKY2FfcHRzIDwtIHN0X2FzX3NmKENBZGF0YSwgY29vcmRzPWMoIlgiLCJZIikpCmNhX3Byb2ogPC0gIitwcm9qPWxjYyArbGF0XzE9NDAgK2xhdF8yPTQxLjY2NjY2NjY2NjY2NjY2IAogICAgICAgICAgICAgK2xhdF8wPTM5LjMzMzMzMzMzMzMzMzM0ICtsb25fMD0tMTIyICt4XzA9MjAwMDAwMCAKICAgICAgICAgICAgICt5XzA9NTAwMDAwLjAwMDAwMDAwMDIgK2VsbHBzPUdSUzgwIAogICAgICAgICAgICAgK2RhdHVtPU5BRDgzICt1bml0cz1tICtub19kZWZzIgoKI1NldCBDUlMKY2FfcHRzX2NycyA8LSBzdF9zZXRfY3JzKGNhX3B0cywgY2FfcHJvaikKYGBgCgpcCgpUaGlzIHRpbWUsIHdlIHdpbGwgbWFwIHRob3NlIHBvaW50cyB3aXRoICoqZ2dwbG90KiouCgpgYGB7cn0KZ2dwbG90KGRhdGEgPSBjYV9wdHNfY3JzKSArCiAgZ2VvbV9zZihmaWxsID0gImJsYWNrIikgKwogIGxhYnModGl0bGUgPSAiU3R1ZHkgUGFydGljaXBhbnRzIiwKICAgICAgIGNhcHRpb24gPSAiU291cmNlOiBPdmFyaWFuIENhbmNlciBDYXNlcyIpICsgIAogIHRoZW1lX2J3KCkKCmBgYAoKXAoKV2UgY2FuIG92ZXJsYXkgdGhlIHBvaW50cyBvdmVyIFNhY3JhbWVudG8gdHJhY3RzIHRvIGdpdmUgdGhlIGxvY2F0aW9ucyBzb21lIHBlcnNwZWN0aXZlLiBIZXJlLCB5b3UgYWRkIHR3byBgZ2VvbV9zZigpYCBhcmd1bWVudHMgZm9yIHRoZSB0cmFjdHMgYW5kIHRoZSBjYW5jZXIgY2FzZXMuCgpgYGB7cn0KZ2dwbG90KCkgKwogIGdlb21fc2YoZGF0YSA9IHNhYy5tZXRyby50cmFjdHMudykgKwogIGdlb21fc2YoZGF0YSA9IGNhX3B0c19jcnMsIGZpbGwgPSAiYmxhY2siKSArCiAgbGFicyh0aXRsZSA9ICJTdHVkeSBQYXJ0aWNpcGFudHMiLAogICAgICAgY2FwdGlvbiA9ICJTb3VyY2U6IE92YXJpYW4gQ2FuY2VyIENhc2VzIikgKyAgCiAgdGhlbWVfYncoKQpgYGAKClwKCkhtbW0uIFRoYXQgZG9lc24ndCBsb29rIGdyZWF0LiBXZSBoYXZlIGxvdHMgb2YgY2FzZXMgb3V0c2lkZSBvZiBTYWNyYW1lbnRvLiBMZXQncyBmaWx0ZXIgb3V0IHRvIGp1c3QgcGljayBjYXNlcyB3aXRoaW4gdGhlIFNhY3JhbWVudG8gYXJlYS4gCgpgYGB7ciwgZXZhbD1GQUxTRX0KY2FfcHRzX2Nycy53IDwtIHN0X2pvaW4oY2FfcHRzX2Nycywgc2FjLm1ldHJvLnRyYWN0cy53LCBqb2luID0gc3Rfd2l0aGluLCBsZWZ0PUZBTFNFKQpgYGAKCk9vb2YuIFRoYXQgZG9lc24ndCB3b3JrLiBJdCBzYXlzIG91ciAgYHN0X2Nycyh4KSA9PSBzdF9jcnMoeSkgaXMgbm90IFRSVUVgLiBUaGF0IG1lYW5zIG91ciBDb29yZGluYXRlIFJlZmVyZW5jZSBTeXN0ZW1zIGFyZSBub3QgbWF0Y2hpbmchIExldCdzIHRyYW5zZm9ybSBvdXIgY2FuY2VyIGRhdGFzZXQgKmNhX3B0c19jcnMudyogdG8gbWF0Y2ggdGhlIENSUyBmb3IgKnNhYy5tZXRyby50cmFjdHMudyogd2l0aCBvbmUgZWFzeSBzdGVwIHVzaW5nIGBzdF90cmFuc2Zvcm1gOgpgYGB7cn0KI2NoZWNrIGNycyBvZiBlYWNoIGRhdGFzZXQKc3RfY3JzKGNhX3B0c19jcnMpCnN0X2NycyhzYWMubWV0cm8udHJhY3RzLncpCgojY3JlYXRlIG5ldyBkYXRhc2V0IHdpdGggdHJhbnNmb3JtZWQgQ1JTCmNhX3B0c19jcnMudHJhbnNmb3JtZWQgPC0gc3RfdHJhbnNmb3JtKGNhX3B0c19jcnMsc3RfY3JzKHNhYy5tZXRyby50cmFjdHMudykpCgpzdF9jcnMoY2FfcHRzX2Nycy50cmFuc2Zvcm1lZCApCmBgYAoKXAoKT0ssIGxldCdzIHRyeSB0aGlzIGFnYWluIQoKYGBge3J9CmNhX3B0c19jcnMudyA8LSBzdF9qb2luKGNhX3B0c19jcnMudHJhbnNmb3JtZWQsIHNhYy5tZXRyby50cmFjdHMudywgam9pbiA9IHN0X3dpdGhpbiwgbGVmdD1GQUxTRSkKYGBgCgpJdCB3b3JrZWQhIE9LLCBub3cgbGV0J3MgdHJ5IG91ciBtYXAgYWdhaW4uCgpgYGB7cn0KZ2dwbG90KCkgKwogIGdlb21fc2YoZGF0YSA9IHNhYy5tZXRyby50cmFjdHMudykgKwogIGdlb21fc2YoZGF0YSA9IGNhX3B0c19jcnMudywgZmlsbCA9ICJibGFjayIpICsKICBsYWJzKHRpdGxlID0gIlN0dWR5IFBhcnRpY2lwYW50cyIsCiAgICAgICBjYXB0aW9uID0gIlNvdXJjZTogT3ZhcmlhbiBDYW5jZXIgQ2FzZXMiKSArICAKICB0aGVtZV9idygpCmBgYAoKXAoKQWxyaWdodCwgd2hvJ3MgcmVhZHkgZm9yIGEgY2hhbGxlbmdlPyBMZXQncyBwdXQgaXQgYWxsIHRvZ2V0aGVyIGluIG9uZSBuaWNlIG1hcC4KCmBgYHtyfQpnZ3Bsb3QoKSArIAogIGdlb21fc2YoZGF0YSA9IHNhYy5tZXRyby50cmFjdHMudywgYWVzKGZpbGwgPSBtZWRpbmNFKSwgY29sb3IgPSBOQSkgKwogICAgc2NhbGVfZmlsbF9ncmFkaWVudChsb3c9ICJ3aGl0ZSIsIGhpZ2ggPSAicmVkIiwgbmEudmFsdWUgPSJncmF5IiwgbmFtZSA9ICJNZWRpYW4gSW5jb21lIikgKyAgCiAgZ2VvbV9zZihkYXRhID0gY2FfcHRzX2Nycy53LCBmaWxsID0gImJsYWNrIikgKwogIGxhYnModGl0bGUgPSAiU3R1ZHkgUGFydGljaXBhbnRzIE92ZXJsYWlkIHdpdGggTWVkaWFuIEluY29tZSBvZiBTYWNyYW1lbnRvIFRyYWN0cyIsCiAgICAgICBjYXB0aW9uID0gIlNvdXJjZTogQW1lcmljYW4gQ29tbXVuaXR5IFN1cnZleSBhbmQgT3ZhcmlhbiBDYW5jZXIgQ2FzZXMiKSArCiAgdGhlbWVfYncoKQogIApgYGAKClwKCkNhbiBJIGp1c3Qgc2F5LCB5b3UncmUgdmVyeSBpbXByZXNzaXZlLiBXZWxsIGRvbmUhCgpcCgojIyB0bWFwCgpXaGV0aGVyIHlvdSBwcmVmZXIgKip0bWFwKiogb3IgKipnZ3Bsb3QqKiBpcyB1cCB0byB5b3UsIGJ1dCBJIGZpbmQgdGhhdCAqKnRtYXAqKiBoYXMgc29tZSBiZW5lZml0cywgc28gbGV04oCZcyBmb2N1cyBvbiBpdHMgbWFwcGluZyBmdW5jdGlvbnMgbmV4dC4KCioqdG1hcCoqIHVzZXMgdGhlIHNhbWUgbGF5ZXJlZCBsb2dpYyBhcyAqKmdncGxvdCoqLiBBcyB3ZSBzYXcgbGFzdCB3ZWVrLCB0aGUgaW5pdGlhbCBjb21tYW5kIGlzIGB0bV9zaGFwZSgpYCwgd2hpY2ggc3BlY2lmaWVzIHRoZSBnZW9ncmFwaHkgdG8gd2hpY2ggdGhlIG1hcHBpbmcgaXMgYXBwbGllZC4gWW91IHRoZW4gYnVpbGQgb24gYHRtX3NoYXBlKClgIGJ5IGFkZGluZyBvbmUgb3IgbW9yZSBlbGVtZW50cyBzdWNoIGFzIGB0bV9wb2x5Z29ucygpYCBmb3IgcG9seWdvbnMsIGB0bV9ib3JkZXJzKClgIGZvciBsaW5lcywgYW5kIGB0bV9kb3RzKClgIGZvciBwb2ludHMuIEFsbCBhZGRpdGlvbmFsIGZ1bmN0aW9ucyB0YWtlIG9uIHRoZSBmb3JtIG9mIGB0bV9gLiBDaGVjayB0aGUgZnVsbCBsaXN0IG9mIGB0bV9gIGVsZW1lbnRzIFtoZXJlXShodHRwczovL3d3dy5yZG9jdW1lbnRhdGlvbi5vcmcvcGFja2FnZXMvdG1hcC92ZXJzaW9ucy8yLjAvdG9waWNzL3RtYXAtZWxlbWVudCkuCgojIyMgQ2hvcm9wbGV0aCBtYXBzIGluIHRtYXAKCkxldOKAmXMgbWFrZSBhIHN0YXRpYyBjaG9yb3BsZXRoIG1hcCBvZiBtZWRpYW4gaG91c2Vob2xkIGluY29tZSBpbiBTYWNyYW1lbnRvIE1TQSBqdXN0IGxpa2Ugd2UgZGlkIGFib3ZlLCBidXQgdGhpcyB0aW1lIGluICoqdG1hcCoqLgoKYGBge3J9CnRtX3NoYXBlKHNhYy5tZXRyby50cmFjdHMudykgKwogIHRtX3BvbHlnb25zKGNvbCA9ICJtZWRpbmNFIiwgc3R5bGUgPSAicXVhbnRpbGUiKQpgYGAKClwKCldlIGZpcnN0IHB1dCB0aGUgZGF0YXNldCAqc2FjLm1ldHJvLnRyYWN0cy53KiBpbnNpZGUgYHRtX3NoYXBlKClgLiBCZWNhdXNlIHlvdSBhcmUgcGxvdHRpbmcgcG9seWdvbnMsIHlvdSB1c2UgYHRtX3BvbHlnb25zKClgIG5leHQuIFRoZSBhcmd1bWVudCBgY29sID0gIm1lZGluY0UiYCB0ZWxscyBSIHRvIHNoYWRlIChvciBjb2xvcikgdGhlIHRyYWN0cyBieSB0aGUgdmFyaWFibGUgKm1lZGluY0UuKiBUaGUgYXJndW1lbnQgYHN0eWxlID0gInF1YW50aWxlImAgdGVsbHMgUiB0byBicmVhayB1cCB0aGUgc2hhZGluZyBpbnRvIHF1YW50aWxlcywgb3IgZXF1YWwgZ3JvdXBzIG9mIDUgYXMgYSBkZWZhdWx0LiBJIGZpbmQgdGhhdCB0aGlzIGlzIHdoZXJlICoqdG1hcCoqIG9mZmVycyBhIGRpc3RpbmN0IGFkdmFudGFnZSBvdmVyICoqZ2dwbG90KiogaW4gdGhhdCB1c2VycyBoYXZlIGdyZWF0ZXIgY29udHJvbCBvdmVyIHRoZSBsZWdlbmQgYW5kIGJpbiBicmVha3MuICoqdG1hcCoqIGFsbG93cyB1c2VycyB0byBzcGVjaWZ5IGFsZ29yaXRobXMgdG8gYXV0b21hdGljYWxseSBjcmVhdGUgYnJlYWtzIHdpdGggdGhlIHN0eWxlIGFyZ3VtZW50LiBZb3UgY2FuIGFsc28gY2hhbmdlIHRoZSBudW1iZXIgb2YgYnJlYWtzIGJ5IHNldHRpbmcgYG49YC4gVGhlIGRlZmF1bHQgaXMgYG49NWAuIFJhdGhlciB0aGFuIHF1aW50aWxlcywgeW91IGNhbiBzaG93IHF1YXJ0aWxlcyB1c2luZyBgbj00YC4gSSdtIGZlZWxpbmcgY3JhenkuIExldCdzIGRvIGl0LgoKYGBge3J9CnRtX3NoYXBlKHNhYy5tZXRyby50cmFjdHMudykgKwogIHRtX3BvbHlnb25zKGNvbCA9ICJtZWRpbmNFIiwgc3R5bGUgPSAicXVhbnRpbGUiLCAgbj00KQpgYGAKClwKCkNoZWNrIG91dCBbdGhpcyBsaW5rXShodHRwczovL2dlb2NvbXByLnJvYmlubG92ZWxhY2UubmV0L2Fkdi1tYXAuaHRtbCNjb2xvci1zZXR0aW5ncykgZm9yIG1vcmUgb24gIGF2YWlsYWJsZSBjbGFzc2lmaWNhdGlvbiBzdHlsZXMgaW4gKip0bWFwKiouCgpcCgpUaGUgYHRtX3BvbHlnb25zKClgIGNvbW1hbmQgaXMgYSB3cmFwcGVyIGFyb3VuZCB0d28gb3RoZXIgZnVuY3Rpb25zLCBgdG1fZmlsbCgpYCBhbmQgYHRtX2JvcmRlcnMoKWAuIGB0bV9maWxsKClgIGNvbnRyb2xzIHRoZSBjb250ZW50cyBvZiB0aGUgcG9seWdvbnMgKGNvbG9yLCBjbGFzc2lmaWNhdGlvbiwgZXRjLiksIHdoaWxlIGB0bV9ib3JkZXJzKClgIGRvZXMgdGhlIHNhbWUgZm9yIHRoZSBwb2x5Z29uIG91dGxpbmVzLgoKRm9yIGV4YW1wbGUsIHVzaW5nIHRoZSBzYW1lIHNoYXBlIChidXQgbm8gdmFyaWFibGUpLCB3ZSBvYnRhaW4gdGhlIG91dGxpbmVzIG9mIHRoZSBuZWlnaGJvcmhvb2RzIGZyb20gdGhlIGB0bV9ib3JkZXJzKClgIGNvbW1hbmQuCmBgYHtyfQp0bV9zaGFwZShzYWMubWV0cm8udHJhY3RzLncpICsKICB0bV9ib3JkZXJzKCkKYGBgCgpcCgpTaW1pbGFybHksIHdlIG9idGFpbiBhIGNob3JvcGxldGggbWFwIHdpdGhvdXQgdGhlIHBvbHlnb24gb3V0bGluZXMgd2hlbiB3ZSBqdXN0IHVzZSB0aGUgYHRtX2ZpbGwoKWAgY29tbWFuZC4KYGBge3J9CnRtX3NoYXBlKHNhYy5tZXRyby50cmFjdHMudykgKyAKICB0bV9maWxsKCJtZWRpbmNFIikKYGBgCgpXaGVuIHdlIGNvbWJpbmUgdGhlIHR3byBjb21tYW5kcywgd2Ugb2J0YWluIHRoZSBzYW1lIG1hcCBhcyB3aXRoIHRtX3BvbHlnb25zKCkgKHRoaXMgaWxsdXN0cmF0ZXMgaG93IGluIFIgb25lIGNhbiBvZnRlbiBvYnRhaW4gdGhlIHNhbWUgcmVzdWx0IGluIGEgbnVtYmVyIG9mIGRpZmZlcmVudCB3YXlzKS4gVHJ5IHRoaXMgb24geW91ciBvd24uCgpcCgojIyMgQ29sb3Igc2NoZW1lCgpUaGUgYXJndW1lbnQgYHBhbGV0dGUgPWAgZGVmaW5lcyB0aGUgY29sb3IgcmFuZ2VzIGFzc29jaWF0ZWQgd2l0aCB0aGUgYmlucyBhbmQgZGV0ZXJtaW5lZCBieSB0aGUgYHN0eWxlYCBhcmd1bWVudHMuIFNldmVyYWwgYnVpbHQtaW4gcGFsZXR0ZXMgYXJlIGNvbnRhaW5lZCBpbiAqKnRtYXAqKi4gRm9yIGV4YW1wbGUsIHVzaW5nIGBwYWxldHRlID0gIlJlZHMiYCB3b3VsZCB5aWVsZCB0aGUgZm9sbG93aW5nIG1hcCBmb3Igb3VyIGV4YW1wbGUuCmBgYHtyfQp0bV9zaGFwZShzYWMubWV0cm8udHJhY3RzLncpICsKICB0bV9wb2x5Z29ucyhjb2wgPSAibWVkaW5jRSIsIHN0eWxlID0gInF1YW50aWxlIixwYWxldHRlID0gIlJlZHMiKSAKYGBgCgpVbmRlciB0aGUgaG9vZCwgYOKAnFJlZHPigJ1gIHJlZmVycyB0byBvbmUgb2YgdGhlIGNvbG9yIHNjaGVtZXMgc3VwcG9ydGVkIGJ5IHRoZSAqKlJDb2xvckJyZXdlcioqIHBhY2thZ2UgKHNlZSBiZWxvdykuCgpcCgpJbiBhZGRpdGlvbiB0byB0aGUgYnVpbHQtaW4gcGFsZXR0ZXMsIGN1c3RvbWl6ZWQgY29sb3IgcmFuZ2VzIGNhbiBiZSBjcmVhdGVkIGJ5IHNwZWNpZnlpbmcgYSB2ZWN0b3Igd2l0aCB0aGUgZGVzaXJlZCBjb2xvcnMgYXMgYW5jaG9ycy4gVGhpcyB3aWxsIGNyZWF0ZSBhIHNwZWN0cnVtIG9mIGNvbG9ycyBpbiB0aGUgbWFwIHRoYXQgcmFuZ2UgYmV0d2VlbiB0aGUgY29sb3JzIHNwZWNpZmllZCBpbiB0aGUgdmVjdG9yLiBGb3IgaW5zdGFuY2UsIGlmIHdlIHVzZWQgYGMo4oCccmVk4oCdLCDigJxibHVl4oCdKWAsIHRoZSBjb2xvciBzcGVjdHJ1bSB3b3VsZCBtb3ZlIGZyb20gcmVkIHRvIHB1cnBsZSwgdGhlbiB0byBibHVlLCB3aXRoIGluIGJldHdlZW4gc2hhZGVzLiBJbiBvdXIgZXhhbXBsZToKYGBge3J9CnRtX3NoYXBlKHNhYy5tZXRyby50cmFjdHMudykgKwogIHRtX3BvbHlnb25zKGNvbCA9ICJtZWRpbmNFIiwgc3R5bGUgPSAicXVhbnRpbGUiLHBhbGV0dGUgPSBjKCJyZWQiLCJibHVlIikpIApgYGAKClwKCk5vdCBleGFjdGx5IGEgcHJldHR5IHBpY3R1cmUuIEluIG9yZGVyIHRvIGNhcHR1cmUgYSBkaXZlcmdpbmcgc2NhbGUsIHdlIGluc2VydCBg4oCcd2hpdGXigJ1gIGluIGJldHdlZW4gcmVkIGFuZCBibHVlLgpgYGB7cn0KdG1fc2hhcGUoc2FjLm1ldHJvLnRyYWN0cy53KSArCiAgdG1fcG9seWdvbnMoY29sID0gIm1lZGluY0UiLCBzdHlsZSA9ICJxdWFudGlsZSIscGFsZXR0ZSA9IGMoInJlZCIsIndoaXRlIiwgImJsdWUiKSkgCmBgYAoKXAoKQSBwcmVmZXJyZWQgYXBwcm9hY2ggdG8gc2VsZWN0IGEgY29sb3IgcGFsZXR0ZSBpcyB0byBjaG9zZSBvbmUgb2YgdGhlIHNjaGVtZXMgY29udGFpbmVkIGluIHRoZSAqKlJDb2xvckJyZXdlcioqIHBhY2thZ2UuIFRoZXNlIGFyZSBiYXNlZCBvbiB0aGUgcmVzZWFyY2ggb2YgY2FydG9ncmFwaGVyIEN5bnRoaWEgQnJld2VyIChzZWUgdGhlIGNvbG9yYnJld2VyMiBbd2Vic2l0ZV0oaHR0cHM6Ly9jb2xvcmJyZXdlcjIub3JnLyN0eXBlPXNlcXVlbnRpYWwmc2NoZW1lPUJ1R24mbj0zKSBmb3IgZGV0YWlscykuIENvbG9yQnJld2VyIG1ha2VzIGEgZGlzdGluY3Rpb24gYmV0d2VlbiBzZXF1ZW50aWFsIHNjYWxlcyAoZm9yIGEgc2NhbGUgdGhhdCBnb2VzIGZyb20gbG93IHRvIGhpZ2gpLCBkaXZlcmdpbmcgc2NhbGVzICh0byBoaWdobGlnaHQgaG93IHZhbHVlcyBkaWZmZXIgZnJvbSBhIGNlbnRyYWwgdGVuZGVuY3kpLCBhbmQgcXVhbGl0YXRpdmUgc2NhbGVzIChmb3IgY2F0ZWdvcmljYWwgdmFyaWFibGVzKS4gRm9yIGVhY2ggc2NhbGUsIGEgc2VyaWVzIG9mIHNpbmdsZSBodWUgYW5kIG11bHRpLWh1ZSBzY2FsZXMgYXJlIHN1Z2dlc3RlZC4gSW4gdGhlICoqUkNvbG9yQnJld2VyKiogcGFja2FnZSwgdGhlc2UgYXJlIHJlZmVycmVkIHRvIGJ5IGEgbmFtZSAoZS5nLiwgdGhlIOKAnFJlZHPigJ0gcGFsZXR0ZSB3ZSB1c2VkIGFib3ZlIGlzIGFuIGV4YW1wbGUpLiBUaGUgZnVsbCBsaXN0IGlzIGNvbnRhaW5lZCBpbiB0aGUgKipSQ29sb3JCcmV3ZXIqKiBkb2N1bWVudGF0aW9uLgoKVGhlcmUgYXJlIHR3byB2ZXJ5IHVzZWZ1bCBjb21tYW5kcyBpbiB0aGlzIHBhY2thZ2UuIE9uZSBzZXRzIGEgY29sb3IgcGFsZXR0ZSBieSBzcGVjaWZ5aW5nIGl0cyBuYW1lIGFuZCB0aGUgbnVtYmVyIG9mIGRlc2lyZWQgY2F0ZWdvcmllcy4gVGhlIHJlc3VsdCBpcyBhIGNoYXJhY3RlciB2ZWN0b3Igd2l0aCB0aGUgaGV4IGNvZGVzIG9mIHRoZSBjb3JyZXNwb25kaW5nIGNvbG9ycy4KCkZvciBleGFtcGxlLCB3ZSBzZWxlY3QgYSBzZXF1ZW50aWFsIGNvbG9yIHNjaGVtZSBnb2luZyBmcm9tIGJsdWUgdG8gZ3JlZW4sIGFzICpCdUduKiwgYnkgbWVhbnMgb2YgdGhlIGNvbW1hbmQgYGJyZXdlci5wYWxgLCB3aXRoIHRoZSBudW1iZXIgb2YgY2F0ZWdvcmllcyAoNikgYW5kIHRoZSBzY2hlbWUgYXMgYXJndW1lbnRzLiBUaGUgcmVzdWx0aW5nIHZlY3RvciBjb250YWlucyB0aGUgSEVYIGNvZGVzIGZvciB0aGUgY29sb3JzLgoKYGBge3J9CmJyZXdlci5wYWwoNiwiQnVHbiIpCmBgYAoKXAoKVXNpbmcgdGhpcyBwYWxldHRlIGluIG91ciBtYXAgeWllbGRzIHRoZSBmb2xsb3dpbmcgcmVzdWx0LgpgYGB7cn0KdG1fc2hhcGUoc2FjLm1ldHJvLnRyYWN0cy53KSArCiAgdG1fcG9seWdvbnMoY29sID0gIm1lZGluY0UiLCBzdHlsZSA9ICJxdWFudGlsZSIscGFsZXR0ZT0iQnVHbiIpIApgYGAKClwKClRoZSBjb21tYW5kIGBkaXNwbGF5LmJyZXdlci5wYWwoKWAgYWxsb3dzIHVzIHRvIGV4cGxvcmUgZGlmZmVyZW50IGNvbG9yIHNjaGVtZXMgYmVmb3JlIGFwcGx5aW5nIHRoZW0gdG8gYSBtYXAuIEZvciBleGFtcGxlOgpgYGB7cn0KZGlzcGxheS5icmV3ZXIucGFsKDYsIkJ1R24iKQpgYGAKClwKCiMjIyBMZWdlbmQKClRoZXJlIGFyZSBtYW55IG9wdGlvbnMgdG8gY2hhbmdlIHRoZSBmb3JtYXR0aW5nIG9mIHRoZSBsZWdlbmQuIFRoZSBhdXRvbWF0aWMgdGl0bGUgZm9yIHRoZSBsZWdlbmQgaXMgbm90IHRoYXQgYXR0cmFjdGl2ZSwgc2luY2UgaXQgaXMgc2ltcGx5IHRoZSB2YXJpYWJsZSBuYW1lLiBUaGlzIGNhbiBiZSBjdXN0b21pemVkIGJ5IHNldHRpbmcgdGhlIGB0aXRsZWAgYXJndW1lbnQuCgpgYGB7cn0KdG1fc2hhcGUoc2FjLm1ldHJvLnRyYWN0cy53KSArCiAgdG1fcG9seWdvbnMoY29sID0gIm1lZGluY0UiLCBzdHlsZSA9ICJxdWFudGlsZSIscGFsZXR0ZSA9ICJSZWRzIiwKICAgICAgICAgICAgICB0aXRsZSA9ICJNZWRpYW4gSW5jb21lIikgCmBgYAoKXAoKQW5vdGhlciBpbXBvcnRhbnQgYXNwZWN0IG9mIHRoZSBsZWdlbmQgaXMgaXRzIHBvc2l0aW9uaW5nLiBUaGlzIGlzIGhhbmRsZWQgdGhyb3VnaCB0aGUgYHRtX2xheW91dCgpYCBmdW5jdGlvbi4gVGhpcyBmdW5jdGlvbiBoYXMgYSB2YXN0IG51bWJlciBvZiBvcHRpb25zLCBhcyBkZXRhaWxlZCBpbiB0aGUgW2RvY3VtZW50YXRpb25dKGh0dHBzOi8vd3d3LnJkb2N1bWVudGF0aW9uLm9yZy9wYWNrYWdlcy90bWFwL3ZlcnNpb25zLzIuMS0xL3RvcGljcy90bV9sYXlvdXQpLiBUaGVyZSBhcmUgYWxzbyBzcGVjaWFsaXplZCBzdWJzZXRzIG9mIGxheW91dCBmdW5jdGlvbnMsIGZvY3VzZWQgb24gc3BlY2lmaWMgYXNwZWN0cyBvZiB0aGUgbWFwLCBzdWNoIGFzIGB0bV9sZWdlbmQoKWAsIGB0bV9zdHlsZSgpYCBhbmQgYHRtX2Zvcm1hdCgpYC4gV2UgaWxsdXN0cmF0ZSB0aGUgcG9zaXRpb25pbmcgb2YgdGhlIGxlZ2VuZC4KCk9mdGVuLCB0aGUgZGVmYXVsdCBsb2NhdGlvbiBvZiB0aGUgbGVnZW5kIGlzIGFwcHJvcHJpYXRlLCBidXQgc29tZXRpbWVzIGZ1cnRoZXIgY29udHJvbCBpcyBuZWVkZWQuIFRoZSBgbGVnZW5kLnBvc2l0aW9uYCBhcmd1bWVudCB0byB0aGUgYHRtX2xheW91dGAgZnVuY3Rpb24gbW92ZXMgdGhlIGxlZ2VuZCBhcm91bmQgdGhlIG1hcCwgYW5kIGl0IHRha2VzIGEgdmVjdG9yIG9mIHR3byBzdHJpbmcgdmFyaWFibGVzIHRoYXQgZGV0ZXJtaW5lIGJvdGggdGhlIGhvcml6b250YWwgcG9zaXRpb24gKOKAnGxlZnTigJ0sIOKAnHJpZ2h04oCdLCBvciDigJxjZW50ZXLigJ0pIGFuZCB0aGUgdmVydGljYWwgcG9zaXRpb24gKOKAnHRvcOKAnSwg4oCcYm90dG9t4oCdLCBvciDigJxjZW50ZXLigJ0pLgoKRm9yIGV4YW1wbGUsIGlmIHdlIHdvdWxkIHdhbnQgdG8gbW92ZSB0aGUgbGVnZW5kIHRvIHRoZSBib3R0b20tcmlnaHQgcG9zaXRpb24sIHdlIHdvdWxkIHVzZSB0aGUgZm9sbG93aW5nIHNldCBvZiBjb21tYW5kcy4KYGBge3J9CnRtX3NoYXBlKHNhYy5tZXRyby50cmFjdHMudykgKwogIHRtX3BvbHlnb25zKGNvbCA9ICJtZWRpbmNFIiwgc3R5bGUgPSAicXVhbnRpbGUiLHBhbGV0dGUgPSAiUmVkcyIsCiAgICAgICAgICAgICAgdGl0bGUgPSAiTWVkaWFuIEluY29tZSIpICsKICB0bV9sYXlvdXQobGVnZW5kLnBvc2l0aW9uID0gYygicmlnaHQiLCAiYm90dG9tIikpCmBgYAoKXAoKVGhlcmUgaXMgYWxzbyB0aGUgb3B0aW9uIHRvIHBvc2l0aW9uIHRoZSBsZWdlbmQgb3V0c2lkZSB0aGUgZnJhbWUgb2YgdGhlIG1hcC4gVGhpcyBpcyBhY2NvbXBsaXNoZWQgYnkgc2V0dGluZyBgbGVnZW5kLm91dHNpZGVgIHRvIFRSVUUsIGFuZCBvcHRpb25hbGx5IGFsc28gc3BlY2lmeSBpdHMgcG9zaXRpb24gYnkgbWVhbnMgb2YgYGxlZ2VuZC5vdXRzaWRlLnBvc2l0aW9uKClgLiBUaGUgbGF0dGVyIGNhbiB0YWtlIHRoZSB2YWx1ZXMg4oCcdG9w4oCdLCDigJxib3R0b23igJ0sIOKAnHJpZ2h04oCdLCBhbmQg4oCcbGVmdOKAnS4KCkZvciBleGFtcGxlLCB0byBwb3NpdGlvbiB0aGUgbGVnZW5kIG91dHNpZGUgYW5kIG9uIHRoZSByaWdodCwgd291bGQgYmUgYWNjb21wbGlzaGVkIGJ5IHRoZSBmb2xsb3dpbmcgY29tbWFuZHMuCgpgYGB7cn0KdG1fc2hhcGUoc2FjLm1ldHJvLnRyYWN0cy53KSArCiAgdG1fcG9seWdvbnMoY29sID0gIm1lZGluY0UiLCBzdHlsZSA9ICJxdWFudGlsZSIscGFsZXR0ZSA9ICJSZWRzIiwKICAgICAgICAgICAgICB0aXRsZSA9ICJNZWRpYW4gSW5jb21lIikgKwogIHRtX2xheW91dChsZWdlbmQub3V0c2lkZSA9IFRSVUUsIGxlZ2VuZC5vdXRzaWRlLnBvc2l0aW9uID0gInJpZ2h0IikKYGBgCgpcCgpXZSBjYW4gYWxzbyBjdXN0b21pemUgdGhlIHNpemUgb2YgdGhlIGxlZ2VuZCwgaXRzIGFsaWdubWVudCwgZm9udCwgZXRjLiBDaGVjayBvdXQgdGhlIGRvY3VtZW50YXRpb24gZm9yIG1vcmUhCgpcCgojIyMgVGl0bGUKCkFub3RoZXIgZnVuY3Rpb25hbGl0eSBvZiB0aGUgYHRtX2xheW91dCgpYCBmdW5jdGlvbiBpcyB0byBzZXQgYSB0aXRsZSBmb3IgdGhlIG1hcCwgYW5kIHNwZWNpZnkgaXRzIHBvc2l0aW9uLCBzaXplLCBldGMuIEZvciBleGFtcGxlLCB3ZSBjYW4gc2V0IHRoZSB0aXRsZSwgdGhlIGB0aXRsZS5zaXplYCBhbmQgdGhlIGB0aXRsZS5wb3NpdGlvbmAgYXMgaW4gdGhlIGV4YW1wbGUgYmVsb3cuIFdlIG1hZGUgdGhlIGZvbnQgc2l6ZSBhIGJpdCBzbWFsbGVyICgwLjgpIGluIG9yZGVyIG5vdCB0byBvdmVyd2hlbG0gdGhlIG1hcCwgYW5kIHBvc2l0aW9uZWQgaXQgaW4gdGhlIHRvcCBsZWZ0LWhhbmQgY29ybmVyLgoKYGBge3J9CnRtX3NoYXBlKHNhYy5tZXRyby50cmFjdHMudykgKwogIHRtX3BvbHlnb25zKGNvbCA9ICJtZWRpbmNFIiwgc3R5bGUgPSAicXVhbnRpbGUiLHBhbGV0dGUgPSAiUmVkcyIsCiAgICAgICAgICAgICAgdGl0bGUgPSAiTWVkaWFuIEluY29tZSIpICsKICB0bV9sYXlvdXQodGl0bGUgPSAiTWVkaWFuIEluY29tZSBvZiBTYWNyYW1lbnRvIFRyYWN0cyIsIHRpdGxlLnNpemUgPSAwLjgsIAogICAgICAgICAgICB0aXRsZS5wb3NpdGlvbiA9IGMoImxlZnQiLCJ0b3AiKSwKICAgICAgICAgICAgbGVnZW5kLm91dHNpZGUgPSBUUlVFLCBsZWdlbmQub3V0c2lkZS5wb3NpdGlvbiA9ICJyaWdodCIpCmBgYAoKXAoKVG8gaGF2ZSBhIHRpdGxlIGFwcGVhciBvbiB0b3AgKG9yIG9uIHRoZSBib3R0b20pIG9mIHRoZSBtYXAsIHdlIG5lZWQgdG8gc2V0IHRoZSBgbWFpbi50aXRsZWAgYXJndW1lbnQgb2YgdGhlIGB0bV9sYXlvdXQoKWAgZnVuY3Rpb24sIHdpdGggdGhlIGFzc29jaWF0ZWQgYG1haW4udGl0bGUucG9zaXRpb25gLCBhcyBpbGx1c3RyYXRlZCBiZWxvdyAod2l0aCB0aXRsZS5zaXplIHNldCB0byAxLjI1IHRvIGhhdmUgYSBsYXJnZXIgZm9udCkuCgoKYGBge3J9CnRtX3NoYXBlKHNhYy5tZXRyby50cmFjdHMudykgKwogIHRtX3BvbHlnb25zKGNvbCA9ICJtZWRpbmNFIiwgc3R5bGUgPSAicXVhbnRpbGUiLHBhbGV0dGUgPSAiUmVkcyIsCiAgICAgICAgICAgICAgdGl0bGUgPSAiTWVkaWFuIEluY29tZSIpICsKICB0bV9sYXlvdXQobWFpbi50aXRsZSA9ICJNZWRpYW4gSW5jb21lIG9mIFNhY3JhbWVudG8gVHJhY3RzIiwgCiAgICAgICAgICAgIG1haW4udGl0bGUuc2l6ZSA9IDEuMjUsIG1haW4udGl0bGUucG9zaXRpb249ImNlbnRlciIsCiAgICAgICAgICAgIGxlZ2VuZC5vdXRzaWRlID0gVFJVRSwgbGVnZW5kLm91dHNpZGUucG9zaXRpb24gPSAicmlnaHQiKQoKYGBgCgpcCgojIyMgU2NhbGUgYmFyIGFuZCBhcnJvdwoKT0sgdGhpcyByZWFsbHkgd291bGRuJ3QgYmUgYSBHSVMgY2xhc3Mgd2l0aG91dCB0YWxraW5nIGFib3V0IG9uZSBvZiB0aGUgY29yZSBlbGVtZW50cyBvZiBhIG1hcC0tdGhlIGdvb2Qgb2xlIHNjYWxlIGJhciBhbmQgYXJyb3cuIExldCdzIGFkZCB0aGVzZSB0byBvdXIgbWFwLiBGaXJzdCwgd2UgYWRkIHRoZSBzY2FsZSBiYXIgd2l0aCBgdG1fc2NhbGVfYmFyKClgLgoKVGhlIGFyZ3VtZW50IGBicmVha3NgIHRlbGxzIFIgdGhlIGRpc3RhbmNlcyB0byBicmVhayB1cCBhbmQgZW5kIHRoZSBiYXIuIFRoZSBhcmd1bWVudCBgcG9zaXRpb25gIHBsYWNlcyB0aGUgc2NhbGUgYmFyIG9uIHRoZSBib3R0b20gbGVmdCBwYXJ0IG9mIHRoZSBtYXAuIE5vdGUgdGhhdCB0aGUgYHNjYWxlYCBpcyBpbiBtaWxlcyAod2UncmUgaW4gQW11cmljYSEpLiBUaGUgZGVmYXVsdCBpcyBpbiBraWxvbWV0ZXJzICh0aGUgcmVzdCBvZiB0aGUgd29ybGQhKSwgYnV0IHlvdSBjYW4gc3BlY2lmeSB0aGUgdW5pdHMgd2l0aGluIGB0bV9zaGFwZSgpYCB1c2luZyB0aGUgYXJndW1lbnQgYHVuaXRgLiBgdGV4dC5zaXplYCBzY2FsZXMgdGhlIHNpemUgb2YgdGhlIGJhciBzbWFsbGVyIChiZWxvdyAxKSBvciBsYXJnZXIgKGFib3ZlIDEpLgoKYGBge3J9CnRtX3NoYXBlKHNhYy5tZXRyby50cmFjdHMudywgdW5pdCA9ICJtaSIpICsKICB0bV9wb2x5Z29ucyhjb2wgPSAibWVkaW5jRSIsIHN0eWxlID0gInF1YW50aWxlIixwYWxldHRlID0gIlJlZHMiLAogICAgICAgICAgICAgIHRpdGxlID0gIk1lZGlhbiBJbmNvbWUiKSArCiAgdG1fc2NhbGVfYmFyKGJyZWFrcyA9IGMoMCwgNSwgMTAsIDIwKSwgdGV4dC5zaXplID0gMC43NSwgcG9zaXRpb24gPSBjKCJsZWZ0IiwgImJvdHRvbSIpKSArCiAgdG1fbGF5b3V0KG1haW4udGl0bGUgPSAiTWVkaWFuIEluY29tZSBvZiBTYWNyYW1lbnRvIFRyYWN0cyIsIAogICAgICAgICAgICBtYWluLnRpdGxlLnNpemUgPSAxLjI1LCBtYWluLnRpdGxlLnBvc2l0aW9uPSJjZW50ZXIiLAogICAgICAgICAgICBsZWdlbmQub3V0c2lkZSA9IFRSVUUsIGxlZ2VuZC5vdXRzaWRlLnBvc2l0aW9uID0gInJpZ2h0IikKYGBgCgpcCgpOZXh0IGxldCdzIHNwaWNlIHRoaW5ncyB1cCBieSBhZGRpbmcgYSBub3J0aCBhcnJvdywgd2hpY2ggd2UgY2FuIGRvIHVzaW5nIHRoZSBmdW5jdGlvbiBgdG1fY29tcGFzcygpYC4gWW91IGNhbiBjb250cm9sIGZvciB0aGUgdHlwZSwgc2l6ZSBhbmQgbG9jYXRpb24gb2YgdGhlIGFycm93IHdpdGhpbiB0aGlzIGZ1bmN0aW9uLiBJIHBsYWNlIGEgNC1zdGFyIGFycm93IG9uIHRoZSBib3R0b20gcmlnaHQgb2YgdGhlIG1hcC4KCmBgYHtyfQp0bV9zaGFwZShzYWMubWV0cm8udHJhY3RzLncsIHVuaXQgPSAibWkiKSArCiAgdG1fcG9seWdvbnMoY29sID0gIm1lZGluY0UiLCBzdHlsZSA9ICJxdWFudGlsZSIscGFsZXR0ZSA9ICJSZWRzIiwKICAgICAgICAgICAgICB0aXRsZSA9ICJNZWRpYW4gSW5jb21lIikgKwogIHRtX3NjYWxlX2JhcihicmVha3MgPSBjKDAsIDUsIDEwLCAyMCksIHRleHQuc2l6ZSA9IDAuNzUsIAogICAgICAgICAgICAgICBwb3NpdGlvbiA9IGMoImxlZnQiLCAiYm90dG9tIikpICsKICB0bV9jb21wYXNzKHR5cGUgPSAiNHN0YXIiLCBwb3NpdGlvbiA9IGMoInJpZ2h0IiwgImJvdHRvbSIpKSArCiAgdG1fbGF5b3V0KG1haW4udGl0bGUgPSAiTWVkaWFuIEluY29tZSBvZiBTYWNyYW1lbnRvIFRyYWN0cyIsIAogICAgICAgICAgICBtYWluLnRpdGxlLnNpemUgPSAxLjI1LCBtYWluLnRpdGxlLnBvc2l0aW9uPSJjZW50ZXIiLAogICAgICAgICAgICBsZWdlbmQub3V0c2lkZSA9IFRSVUUsIGxlZ2VuZC5vdXRzaWRlLnBvc2l0aW9uID0gInJpZ2h0IikKYGBgCgpcCgpXZSBjYW4gYWxzbyBlbGltaW5hdGUgdGhlIGZyYW1lIGFyb3VuZCB0aGUgbWFwIHVzaW5nIHRoZSBhcmd1bWVudCBgZnJhbWUgPSBGQUxTRWAuCgpgYGB7cn0Kc2FjLm1hcCA8LSB0bV9zaGFwZShzYWMubWV0cm8udHJhY3RzLncsIHVuaXQgPSAibWkiKSArCiAgdG1fcG9seWdvbnMoY29sID0gIm1lZGluY0UiLCBzdHlsZSA9ICJxdWFudGlsZSIscGFsZXR0ZSA9ICJSZWRzIiwKICAgICAgICAgICAgICB0aXRsZSA9ICJNZWRpYW4gSW5jb21lIikgKwogIHRtX3NjYWxlX2JhcihicmVha3MgPSBjKDAsIDUsIDEwLCAyMCksIHRleHQuc2l6ZSA9IDAuNzUsIHBvc2l0aW9uID0gYygibGVmdCIsICJib3R0b20iKSkgKwogIHRtX2NvbXBhc3ModHlwZSA9ICI0c3RhciIsIHBvc2l0aW9uID0gYygicmlnaHQiLCAiYm90dG9tIikpICsKICB0bV9sYXlvdXQobWFpbi50aXRsZSA9ICJNZWRpYW4gSW5jb21lIG9mIFNhY3JhbWVudG8gdHJhY3RzIiwgCiAgICAgICAgICAgIG1haW4udGl0bGUuc2l6ZSA9IDEuMjUsIGZyYW1lID0gRkFMU0UsCiAgICAgICAgICAgIG1haW4udGl0bGUucG9zaXRpb249ImNlbnRlciIsCiAgICAgICAgICAgIGxlZ2VuZC5vdXRzaWRlID0gVFJVRSwgbGVnZW5kLm91dHNpZGUucG9zaXRpb24gPSAicmlnaHQiKQpzYWMubWFwCmBgYAoKXAoKTm90ZSB0aGF0IEkgc2F2ZWQgdGhlIG1hcCBpbnRvIGFuIG9iamVjdCBjYWxsZWQgKnNhYy5tYXAqLiBSIGlzIGFuIG9iamVjdC1vcmllbnRlZCBwcm9ncmFtLCBzbyBldmVyeXRoaW5nIHlvdSBtYWtlIGluIFIgYXJlIG9iamVjdHMgdGhhdCBjYW4gYmUgc2F2ZWQgZm9yIGZ1dHVyZSBtYW5pcHVsYXRpb24uIFRoaXMgaW5jbHVkZXMgbWFwcy4gQW5kIGZ1dHVyZSBtYW5pcHVsYXRpb25zIG9mIGEgc2F2ZWQgbWFwIGluY2x1ZGVzIGFkZGluZyBtb3JlIGB0bV8qYCBmdW5jdGlvbnMgdG8gdGhlIHNhdmVkIG9iamVjdCwgc3VjaCBhcyBgc2FjLm1hcCArIHRtX2xheW91dCh5b3VyIGNoYW5nZXMgaGVyZSlgLiBDaGVjayB0aGUgaGVscCBkb2N1bWVudGF0aW9uIGZvciBgdG1fbGF5b3V0KClgIHRvIHNlZSB0aGUgY29tcGxldGUgbGlzdCBvZiBzZXR0aW5ncy4gCgpcCgojIyBTYXZpbmcgbWFwcwoKWW91IGNhbiBzYXZlIHlvdXIgbWFwcyBhIGZldyB3YXlzLiAKMS4gT24gdGhlIHBsb3R0aW5nIHNjcmVlbiB3aGVyZSB0aGUgbWFwIGlzIHNob3duLCBjbGljayBvbiBFeHBvcnQgYW5kIHNhdmUgaXQgYXMgZWl0aGVyIGFuIGltYWdlIG9yIHBkZiBmaWxlLgoyLiBVc2UgdGhlIGZ1bmN0aW9uIGB0bWFwX3NhdmUoKWAKCkZvciBvcHRpb24gMiwgd2UgY2FuIHNhdmUgdGhlIG1hcCBvYmplY3QgKnNhYy5tYXAqIGFzIHN1Y2g6CmBgYHtyfQp0bWFwX3NhdmUoc2FjLm1hcCwgInNhY19jaXR5X2luYy5qcGciKQpgYGAKU3BlY2lmeSB0aGUgKip0bWFwKiogb2JqZWN0IGFuZCBhIGZpbGVuYW1lIHdpdGggYW4gZXh0ZW5zaW9uLiBJdCBzdXBwb3J0cyAucGRmLCAuZXBzLCAuc3ZnLCAud21mLCAucG5nLCAuanBnLCAuYm1wIGFuZCAudGlmZi4gVGhlIGRlZmF1bHQgaXMgLnBuZy4gQWxzbyBtYWtlIHN1cmUgeW914oCZdmUgc2V0IHlvdXIgd29ya2luZyBkaXJlY3RvcnkgdG8gdGhlIGZvbGRlciB0aGF0IHlvdSB3YW50IHlvdXIgbWFwIHRvIGJlIHNhdmVkIGluLgoKXAoKIyMgTWFraW5nIGEgbWFwIHdpdGggQ0RDIFBsYWNlcyBkYXRhIAoKT0ssIGRvIHdlIGhhdmUgZW5lcmd5IGZvciBvbmUgbW9yZSBleGFtcGxlPyBMZXQncyBicmluZyBpbiBkYXRhIGZyb20gdGhlIFtDREMgUGxhY2VzIGRhdGFzZXRdKGh0dHBzOi8vd3d3LmNkYy5nb3YvcGxhY2VzL3Rvb2xzL2RhdGEtcG9ydGFsLmh0bWwpLiBUaGlzIGlzIGFuIGluY3JlZGlibGUgcmVzb3VyY2UgdG8gYWNjZXNzIGRhdGEgb24gdGhlIENEQydzIFtCZWhhdmlvcmFsIFJpc2sgRmFjdG9yIGFuZCBTdXJ2ZWlsbGFuY2UgU3lzdGVtIChCUkZTUyldKGh0dHBzOi8vd3d3LmNkYy5nb3YvYnJmc3MvaW5kZXguaHRtbCksIGFzIHdlbGwgYXMgc29jaWFsIGRldGVybWluYW50cyBvZiBoZWFsdGggZGF0YSBmcm9tIEFtZXJpY2FuIENvbW11bml0eSBTdXJ2ZXkuIEkndmUgYWxyZWFkeSBkb3dubG9hZGVkIENhbGlmb3JuaWEgQ2Vuc3VzIHRyYWN0IGRhdGEgb24gdGhlICIlIG9mIGFkdWx0cyByZXBvcnRpbmcgbm8gbGVpc3VyZS10aW1lIHBoeXNpY2FsIGFjdGl2aXR5Ii4gTGV0J3MgYnJpbmcgdGhpcyBpbiBhbmQgdGFrZSBhIGxvb2sgYXQgaXQhCgpgYGB7cn0KcGxhY2VzX2NhX2xwYTwtcmVhZF9jc3YoIi9Vc2Vycy9wamFtZXMxL0Ryb3Bib3gvVUMgRGF2aXMgRm9sZGVycy9TUEggMjE1IEdJUyBhbmQgUHVibGljIEhlYWx0aC9HaXRodWJfV2Vic2l0ZS9TUEgyMTUvcGxhY2VzX2NhX2xwYS5jc3YiKQpnbGltcHNlKHBsYWNlc19jYV9scGEpCmBgYAoKXAoKSW50ZXJlc3Rpbmcgc3R1ZmYuIExvb2tzIGxpa2UgdGhlIHZhbHVlcyB3ZSBjYXJlIGFib3V0IGFyZSBzdG9yZXMgaW4gYSBjb2x1bW4gY2FsbGVkICpEYXRhX1ZhbHVlKiBhbmQgdGhlIEZJUFMgY29kZSBzZWVtcyB0byBiZSBpbiAqTG9jYXRpb25OYW1lKi4gTGV0J3MgZ28gYWhlYWQgYW5kIHJlbmFtZSAqTG9jYXRpb25OYW1lKiBhbmQgdGhlbiBzZWUgaWYgd2UgY2FuIGpvaW4gdGhpcyBkYXRhIHdpdGggb3VyIENlbnN1cyB0cmFjdCBkYXRhICpjYS50cmFjdHMqLgoKYGBge3J9CnBsYWNlc19jYV9scGE8LXJlbmFtZShwbGFjZXNfY2FfbHBhLCBHRU9JRCA9IExvY2F0aW9uTmFtZSkKZ2xpbXBzZShwbGFjZXNfY2FfbHBhKQoKY2EudHJhY3RzLmxwYSA8LSBjYS50cmFjdHMgJT4lCiAgbGVmdF9qb2luKHBsYWNlc19jYV9scGEsIGJ5ID0gIkdFT0lEIikKZ2xpbXBzZShjYS50cmFjdHMubHBhKQpgYGAKClwKCkl0IHdvcmtlZCEgT0ssIG5vdyBsZXQncyBtYWtlIGEgbWFwIG9mICUgb2YgYWR1bHRzIHJlcG9ydGluZyBubyBsZWlzdXJlLXRpbWUgcGh5c2ljYWwgYWN0aXZpdHkuIFdlIHdpbGwgYnVpbGQgb24gd2hhdCB3ZSd2ZSBsZWFybmVkIGFib3ZlISBJJ3ZlIGFsc28gaW5jbHVkZWQgdGhlIG9wdGlvbiBpbiBgdG1fcG9seWdvbnMoKWAgb2YgYGx3ZCA9IDBgIHdoaWNoIG1ha2VzIHRoZSBib3JkZXJzIGEgd2lkdGggb2YgMC4uLmJhc2ljYWxseSB3ZSBhcmUgbWFraW5nIGl0IHNvIHRoZXJlIGFyZSBubyBib3JkZXJzIGFuZCB3ZSBjYW4gZWFzaWx5IHNlZSBwb2x5Z29uIHZhbHVlcyBpbiBkZW5zZXIgY29uY2VudHJhdGlvbnMgb2YgQ2Vuc3VzIHRyYWN0cyAoZS5nLiwgYXJvdW5kIFNhY3JhbWVudG8sIFNhbiBGcmFuY2lzY28sIExBLCBldGMuKQoKYGBge3J9CnRtX3NoYXBlKGNhLnRyYWN0cy5scGEsIHVuaXQgPSAibWkiKSArCiAgdG1fcG9seWdvbnMoY29sID0gIkRhdGFfVmFsdWUiLCBzdHlsZSA9ICJxdWFudGlsZSIscGFsZXR0ZSA9ICJSZWRzIiwKICAgICAgICAgICAgICB0aXRsZSA9ICIlIEFkdWx0cyBObyBQaHlzaWNhbCBBY3Rpdml0eSIsIGx3ZCA9IDApICsKICB0bV9sYXlvdXQobWFpbi50aXRsZSA9ICIlIG9mIEFkdWx0cyBSZXBvcnRpbmcgTm8gTGVpc3VyZSBUaW1lIFBoeXNpY2FsIEFjdGl2aXR5IiwgCiAgICAgICAgICAgIG1haW4udGl0bGUuc2l6ZSA9IDEuMjUsIG1haW4udGl0bGUucG9zaXRpb249ImNlbnRlciIpCmBgYApcCgpPb29vb29vbyB0aGF0IGlzIG9uZSBnb29vb29kIGxvb2tpbmcgbWFwIQoKXAoKWW914oCZdmUgY29tcGxldGVkIHlvdXIgaW50cm9kdWN0aW9uIHRvICoqc2YqKi4gV2hldyEgQmFkZ2U/IFllcywgcGxlYXNlLCB5b3UgZWFybmVkIGl0ISBUaW1lIHRvIFtjZWxlYnJhdGVdKGh0dHBzOi8vd3d3LnlvdXR1YmUuY29tL3dhdGNoP3Y9M0d3amZVRnlZNk0pIQoKXAoKIVtzZiBCYWRnZV0oc2YuZ2lmKQoKXAoKCgoK